diff --git a/.github/.copilot-collections.yaml b/.github/.copilot-collections.yaml index 82fe89a58..37520562b 100644 --- a/.github/.copilot-collections.yaml +++ b/.github/.copilot-collections.yaml @@ -1,7 +1,7 @@ # https://github.com/canonical/copilot-collections copilot: - version: v0.13.0 + version: v0.15.0 collections: - starcraft-core - starcraft-docs diff --git a/.github/instructions/cloud-agent.instructions.md b/.github/instructions/cloud-agent.instructions.md new file mode 100644 index 000000000..9fccbd928 --- /dev/null +++ b/.github/instructions/cloud-agent.instructions.md @@ -0,0 +1,73 @@ +--- +applyTo: '**' +excludeAgent: 'code-review' +description: 'Guidelines for GitHub Copilot cloud agent when working in Starcraft.' +--- + +# GitHub Cloud Agent Instructions for Starcraft + +These instructions guide GitHub Copilot cloud agent when it works in Starcraft projects. These don't apply to the initial review provided by GitHub Copilot code review. + +## Code changes in pull request comments + +These instructions apply to interactions between users and the cloud agent, when they mention it with `@copilot`. + +### Draft changes when explicitly requested + +When a user mentions `@copilot` in a comment asking to draft or propose a code change, reply in a comment using GitHub's code suggestion syntax. Do not commit directly to the branch. + +Examples of requests that should trigger this behavior include: + +- "propose a change" +- "show me what this would look like" +- "what would this look like?" +- "can you draft that?" +- "show me an example" +- "write that out" +- "what do you suggest?" +- "show a fix for this" +- "give me a suggestion" +- "how would you write this?" + +Reply using GitHub's code suggestion syntax: + +````markdown +```suggestion + +``` +```` + +This allows the user to review and apply the suggestion themselves with a single click. + +### Only commit changes when explicitly requested + +When a user mentions `@copilot` in a comment asking to finalize a code change, commit the change as requested. + +If no prior suggestion exists in the current PR conversation, propose the change first using the suggestion syntax above before committing. + +Examples of requests that should trigger this behavior include: + +- "commit what is proposed" +- "push this code" +- "apply these changes" +- "go ahead and commit" +- "push the changes" +- "commit the suggestion" +- "apply the fix" +- "push what you suggested" +- "land these changes" + + +### Combined implement and commit requests + +If a user asks for a change **and** explicitly requests it be committed in the same message, treat it as an explicit commit request. Implement the change and commit it directly without posting a suggestion first. + +Examples of requests that should trigger this behavior include: + +- "implement X and push it" +- "fix this and commit" +- "make this change and push" +- "do X and land it" +- "apply X and commit" +- "patch this and commit" +- "commit a fix for this" diff --git a/.github/instructions/starcraft-docs-meta.instructions.md b/.github/instructions/starcraft-docs-meta.instructions.md index 9edf7c32f..e3a6fc88b 100644 --- a/.github/instructions/starcraft-docs-meta.instructions.md +++ b/.github/instructions/starcraft-docs-meta.instructions.md @@ -23,58 +23,38 @@ The meta description must be one line, because Sphinx options don't support line ## Write a meta description -**Do** write the meta description *after* the page is written. The meta description is a reflection of what's actually on the page. +Write the meta description after the page is written. The meta description is a reflection of what's actually on the page, not what the page aspires to be. -**Do** reuse text from the page itself when it's convenient. +Start with the Diátaxis category or action, such as *How to* or *Reference for*. Next, describe what the user will find on the page. Reuse text from the page itself where it's appropriate. -**Do** share descriptions between documents with the same formats. For example, use the same meta description for every page of release notes, but replace the version number. +Include a focus term or phrase, the key string that users are likely to search for. It can be a single word like *security*, or a short phrase such as *create an Ubuntu Core image*. Prefer widely recognized terminology. If your product isn't yet well known, then it's safer to use terms familiar to a global technical audience over product-specific ones. -**Do** frame sentences in terms of *actions* and *facts*. +Make every word count. There is no hard minimum length, but it should be longer and more descriptive than the title, and it should not merely repeat the wording of the title. The maximum length is 160 characters, because search engines truncate the snippet after 160 characters. This includes spaces. - - - - - - - - - - - - - - - - - -
TypeStarts with phrases like...

Action

Get started with

-

Learn about

-

Find information about

Fact

The encabulator is a circuit that

-

The Chef init profile sets up a basic project file for

-

Starcraft is compatible with

+For pages with the same format, share descriptions between them. For example, use the same meta description for every page of release notes, but change the version number. -**Do** put key terms near the start. +### Avoid these mistakes -**Do** aim for an average of 140 characters. Cut any detail about the topic that isn't essential to helping the user make a decision. +Avoid calls to action like *find*, *discover*, *explore*, or *read*. These are so overused that they have become noise words. The user's needs and actions in the moment are out of your control; the snippet should be a glimpse of the information on the page. Tutorials are the exception, where *learn* is appropriate. -**Don't** exceed 160 characters. Search engines will cut the snippet short. +Avoid qualities or claims about the product and its features, like *simple*, *easy*, or *best-in-class*. Marketing rhetoric, sales pitches, and business clichés like the following aren't appropriate for documentation: -**Don't** write to market, motivate, or compel. Marketing prose with rhetoric like the following isn't appropriate for documentation. +*Don't just write code; deliver a product. Start your journey today and turn your project into a production-ready, evergreen, blazing-fast, full-stack app.* -> Don't just write code; deliver a product. Start your journey today and turn your project into a production-ready snap. +### Examples -**Don't** include qualities or claims like *simple*, *easy*, or *best-in-class*. +For _Tutorial_ > _Craft a snap_ in Snapcraft: -**Don't** include key terms that aren't on the page. +> Learn the essentials of Snapcraft. In this tutorial, we package a small Python app into a snap. -**Don't** use pronouns such as *you*, *your*, *we*, or *our*. These waste space and don't match the tone of a description. +For _How to_ > _Change from core24 to core26_ in Snapcraft: -## Example +> How to migrate a snap from core24 to core26. -This is an example of a good meta description for *Set up Starcraft*. +For the _Reference_ > _GNOME extension_ in Snapcraft: -``` rst -.. meta:: - :description: Learn how to install Starcraft on a local system. Starcraft is available as a snap on all GNU/Linux systems that support systemd. -``` +> Reference documentation for the GNOME extension, which adds the components for desktop apps that require GTK 3, GNOME 42 and higher, and GLib. + +For _Explanation_ > _Parts lifecycle_ in Snapcraft: + +> Explanation of the build process of a part in Snapcraft, with a breakdown of the individual steps and the effect each has on the snap filesystem. diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 66cb2f6bd..909446b71 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,3 +1,4 @@ { extends: ["github>canonical/starflow:renovate.json5"], + ignorePaths: ["docs/**"], } diff --git a/.github/workflows/check-renovate.yaml b/.github/workflows/check-renovate.yaml index cc08f8370..e7957f098 100644 --- a/.github/workflows/check-renovate.yaml +++ b/.github/workflows/check-renovate.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install node uses: actions/setup-node@v6 with: diff --git a/.github/workflows/copilot-collections-update.yaml b/.github/workflows/copilot-collections-update.yaml index df28b1aff..76e99ce82 100644 --- a/.github/workflows/copilot-collections-update.yaml +++ b/.github/workflows/copilot-collections-update.yaml @@ -4,6 +4,10 @@ on: - cron: "0 9 * * 1" # Run every Monday at 09:00 UTC workflow_dispatch: +permissions: + contents: write + pull-requests: write + jobs: check-update: # Pinned to @main to get the latest logic, but the content version is controlled by .github/.copilot-collections.yaml diff --git a/.github/workflows/policy.yaml b/.github/workflows/policy.yaml index ef184b823..96b8908e6 100644 --- a/.github/workflows/policy.yaml +++ b/.github/workflows/policy.yaml @@ -13,7 +13,11 @@ jobs: outputs: source-files: ${{ steps.filter.outputs.source-files }} steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + - name: Checkout code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 id: filter with: # Combine multiple negation checks into a union (¬A ∧ ¬B ∧ ¬C), because individual entries are checked independently (A ∨ B ∨ C). @@ -36,6 +40,10 @@ jobs: # 2. Ignore requirements files in spread tests, as some of these intentionally # contain vulnerable versions. # 3. Docs contain requirements.txt files that don't specify versions. - requirements-find-args: '! -name requirements-noble.txt ! -path "./tests/spread/*" ! -path "./docs/**"' - # osv-extra-args: "--config=source/osv-scanner.toml" - uv-export: false + osv-extra-args: "--config=osv-scanner.toml" + osv-exclude-paths: | + docs + tests/spread + uv-export-no-groups: | + docs + docs-starter-pack diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index a2dc0416c..3efd87bf2 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -17,10 +17,10 @@ jobs: source-files: ${{ steps.filter.outputs.source-files }} steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 id: filter with: # Combine multiple negation checks into a union (¬A ∧ ¬B ∧ ¬C), because individual entries are checked independently (A ∨ B ∨ C). diff --git a/.github/workflows/release-publish.yaml b/.github/workflows/release-publish.yaml index fbd936b05..a2bb15603 100644 --- a/.github/workflows/release-publish.yaml +++ b/.github/workflows/release-publish.yaml @@ -14,7 +14,7 @@ jobs: runs-on: [self-hosted, jammy] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Fetch tag annotations run: | # Note: we fetch the tags here instead of using actions/checkout's "fetch-tags" diff --git a/.github/workflows/schema.yaml b/.github/workflows/schema.yaml index 6464d1391..d3f09653d 100644 --- a/.github/workflows/schema.yaml +++ b/.github/workflows/schema.yaml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/spread-docs.yaml b/.github/workflows/spread-docs.yaml index 22639769a..b537d1fcd 100644 --- a/.github/workflows/spread-docs.yaml +++ b/.github/workflows/spread-docs.yaml @@ -2,7 +2,7 @@ name: Spread tests for docs on: pull_request: paths: - - .github/workflow/spread-docs.yaml + - .github/workflows/spread-docs.yaml - spread.yaml - .readthedocs.yaml - docs/**/code/** @@ -21,7 +21,7 @@ jobs: runs-on: [self-hosted, linux, amd64, noble, medium] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Build snap @@ -39,7 +39,7 @@ jobs: systems: ${{ steps.select.outputs.systems }} steps: - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: true @@ -64,7 +64,7 @@ jobs: sudo ip6tables -P FORWARD ACCEPT - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: true @@ -75,7 +75,7 @@ jobs: mkdir "${{ github.workspace }}" - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: true @@ -92,7 +92,7 @@ jobs: - name: Install spread run: | - go install github.com/snapcore/spread/cmd/spread@latest + go install github.com/canonical/spread/cmd/spread@latest - name: Run spread run: | diff --git a/.github/workflows/spread-manual.yaml b/.github/workflows/spread-manual.yaml index 253dc44a1..fa988beb8 100644 --- a/.github/workflows/spread-manual.yaml +++ b/.github/workflows/spread-manual.yaml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 # speed up build times by using wheels, when available @@ -84,7 +84,7 @@ jobs: rm -rf "${{ github.workspace }}" mkdir "${{ github.workspace }}" - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: true diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 5e0b659cb..d9b2ae518 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -46,10 +46,10 @@ on: jobs: snap-build: - runs-on: [self-hosted, amd64] + runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Build snap @@ -62,19 +62,19 @@ jobs: path: ${{ steps.charmcraft.outputs.snap }} spread-select: - runs-on: spread-installed + runs-on: [self-hosted, spread-enabled] outputs: systems: ${{ steps.select.outputs.systems }} steps: - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Find spread systems id: select run: | - echo systems=$(spread -list google: | cut -d: -f2 | sort | uniq | jq --raw-input . | jq --slurp --compact-output) | tee -a "${GITHUB_OUTPUT}" + echo systems=$(spread -list openstack: | cut -d: -f2 | sort | uniq | jq --raw-input . | jq --slurp --compact-output) | tee -a "${GITHUB_OUTPUT}" snap-tests: - runs-on: spread-installed + runs-on: [self-hosted, spread-enabled] needs: [snap-build, spread-select] strategy: fail-fast: false @@ -88,7 +88,7 @@ jobs: mkdir "${{ github.workspace }}" - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: true @@ -100,7 +100,7 @@ jobs: - name: Run spread run: | - spread google:${{ matrix.system }} + spread openstack:${{ matrix.system }} - name: Discard spread workers if: always() @@ -111,13 +111,13 @@ jobs: done snap-store-tests: - runs-on: spread-installed + runs-on: [self-hosted, spread-enabled] needs: [snap-build] steps: - if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login == 'canonical') || (github.event_name == 'push' && github.ref == 'refs/heads/main') name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: true @@ -134,5 +134,16 @@ jobs: CHARMCRAFT_AUTH: ${{ secrets.CHARMCRAFT_AUTH }} CHARMCRAFT_SINGLE_CHARM_AUTH: ${{ secrets.CHARMCRAFT_SINGLE_CHARM_AUTH }} CHARM_DEFAULT_NAME: gh-ci-charmcraft-charm + STAGING_SSO_USERNAME: ${{ vars.STAGING_SSO_USERNAME }} + STAGING_SSO_PASSWORD: ${{ secrets.STAGING_SSO_PASSWORD }} + CHARMCRAFT_STORE_SSO_URL: https://login.staging.ubuntu.com run: | - spread google:ubuntu-22.04-64:tests/spread/store/ + spread openstack:ubuntu-22.04-64:tests/spread/store/ + + - name: Discard spread workers + if: always() + run: | + shopt -s nullglob + for r in .spread-reuse.*.yaml; do + spread -discard -reuse-pid="$(echo "$r" | grep -o -E '[0-9]+')" + done diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3fb59052b..e9af69fdf 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -51,10 +51,10 @@ on: jobs: snap-build: - runs-on: [self-hosted, amd64] + runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Build snap diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da63a53ca..45c9c4428 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: mixed-line-ending - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.17" + rev: "v0.15.22" hooks: # Run the linter - id: ruff diff --git a/README.md b/README.md index a094bc063..a63e92e4d 100644 --- a/README.md +++ b/README.md @@ -101,5 +101,5 @@ Charmcraft is released under the [Apache-2.0 license](LICENSE) [tests-results]: https://github.com/canonical/charmcraft/actions/workflows/tests.yaml [spread-badge]: https://github.com/canonical/charmcraft/actions/workflows/spread.yaml/badge.svg?event=push [spread-results]: https://github.com/canonical/charmcraft/actions/workflows/spread.yaml -[weekly-spread-badge]: https://github.com/canonical/charmcraft/actions/workflows/spread-large.yaml/badge.svg -[weekly-spread-results]: https://github.com/canonical/charmcraft/actions/workflows/spread-large.yaml +[weekly-spread-badge]: https://github.com/canonical/charmcraft/actions/workflows/spread-manual.yaml/badge.svg +[weekly-spread-results]: https://github.com/canonical/charmcraft/actions/workflows/spread-manual.yaml diff --git a/charmcraft/application/commands/init.py b/charmcraft/application/commands/init.py index 581e1f24e..6e9aa9cd8 100644 --- a/charmcraft/application/commands/init.py +++ b/charmcraft/application/commands/init.py @@ -28,6 +28,10 @@ from charmcraft.utils import get_templates_environment, make_executable # the available profiles and in which directory the template can be found +# NOTE: init--framework-26.04 template dirs exist but are intentionally +# not wired into PROFILES yet. They are staged for when ubuntu@26.04 + the V2 +# (uv-based) 12-factor extensions become the default. Wiring them in will also +# require handling the experimental-extensions gating during `init`/`tox` tests. PROFILES = { "kubernetes": "init-kubernetes", "machine": "init-machine", diff --git a/charmcraft/application/commands/lifecycle.py b/charmcraft/application/commands/lifecycle.py index 22de3d23a..8093c3625 100644 --- a/charmcraft/application/commands/lifecycle.py +++ b/charmcraft/application/commands/lifecycle.py @@ -62,10 +62,10 @@ class PackCommand(lifecycle.PackCommand): upload it to Charmhub with `charmcraft upload`. For the charm you must be inside a charm directory with a valid - `metadata.yaml`, `requirements.txt` including the `ops` package - for the Python operator framework, and an operator entrypoint, - usually `src/charm.py`. See `charmcraft init` to create a - template charm directory structure. + `charmcraft.yaml` containing the charm metadata, a `requirements.txt` + including the `ops` package for the Python operator framework, and an + operator entrypoint, usually `src/charm.py`. See `charmcraft help init` + to create a template charm directory structure. """ ) diff --git a/charmcraft/application/commands/remote.py b/charmcraft/application/commands/remote.py index fda63b0e3..6987023cf 100644 --- a/charmcraft/application/commands/remote.py +++ b/charmcraft/application/commands/remote.py @@ -106,7 +106,7 @@ def _run(self, parsed_args: argparse.Namespace, **kwargs: Any) -> int | None: return 77 # permission denied from sysexits.h builder = self._services.remote_build - project = cast(models.Charm, self._services.project) + project = cast("models.Charm", self._services.get("project").get()) config = cast(dict[str, Any], self.config) project_dir = ( pathlib.Path(config.get("global_args", {}).get("project_dir") or ".") diff --git a/charmcraft/application/commands/store.py b/charmcraft/application/commands/store.py index beaeaaa1f..47e5ddbb7 100644 --- a/charmcraft/application/commands/store.py +++ b/charmcraft/application/commands/store.py @@ -36,7 +36,7 @@ from craft_cli import ArgumentParsingError, emit from craft_cli.errors import CraftError from craft_store import attenuations, models, publisher -from craft_store.errors import CredentialsUnavailable +from craft_store.errors import CredentialsUnavailable, UbuntuOneOtpRequiredError from craft_store.models import ResponseCharmResourceBase from humanize import naturalsize from tabulate import tabulate @@ -76,6 +76,14 @@ class _ResourceType(typing.NamedTuple): getattr(attenuations, x) for x in dir(attenuations) if x.isupper() } BUNDLE_REGISTRATION_REMOVAL_URL = "https://discourse.charmhub.io/t/15344" +CHARMLIBS_DEPRECATION_WARNING = ( + "WARNING: Charmhub-hosted charm libraries are deprecated. " + "Go to https://ubu.link/charmhub-libraries-deprecation for more information." +) + + +def _emit_charmlibs_deprecation_warning() -> None: + emit.progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) class LoginCommand(CharmcraftCommand): @@ -87,9 +95,9 @@ class LoginCommand(CharmcraftCommand): """ Login to Charmhub. - Charmcraft will provide a URL for the Charmhub login. When you have - successfully logged in, Charmcraft will store a token for ongoing - access to Charmhub at the CLI (if `--export` option was not used + Charmcraft will prompt for your Ubuntu One email address and password. + When you have successfully logged in, Charmcraft will store a token for + ongoing access to Charmhub at the CLI (if `--export` option was not used otherwise it will only save the credentials in the indicated file). If `--export ` option is used, a secret credentials file will @@ -206,15 +214,47 @@ def run(self, parsed_args): or None ) + if not parsed_args.export and os.getenv(const.ALTERNATE_AUTH_ENV_VAR): + raise CraftError( + f"Cannot login when using alternative auth through " + f"{const.ALTERNATE_AUTH_ENV_VAR} environment variable." + ) + + email = emit.prompt("Email address: ") + password = emit.prompt("Password: ", hide=True) + store = cast(StoreService, self._services.get("store")) if parsed_args.export: - credentials = store.get_credentials(packages=packages, **kwargs) + try: + credentials = store.get_credentials( + email=email, password=password, packages=packages, **kwargs + ) + except UbuntuOneOtpRequiredError: + otp = emit.prompt("One-time password: ") + credentials = store.get_credentials( + email=email, password=password, otp=otp, packages=packages, **kwargs + ) parsed_args.export.write_text(credentials) emit.message( f"Login successful. Credentials exported to {str(parsed_args.export)!r}." ) else: - store.login(packages=packages, **kwargs) + try: + store.login( + email=email, + password=password, + packages=packages, + **kwargs, + ) + except UbuntuOneOtpRequiredError: + otp = emit.prompt("One-time password: ") + store.login( + email=email, + password=password, + otp=otp, + packages=packages, + **kwargs, + ) username = store.get_account_info()["username"] emit.message(f"Logged in as {username!r}.") @@ -265,7 +305,7 @@ def run(self, parsed_args: argparse.Namespace) -> None: """Run the command.""" store = cast("StoreService", self._services.get("store")) try: - macaroon_info = store.client.whoami() + macaroon_info = store.whoami() except CredentialsUnavailable: if parsed_args.format: info = {"logged": False} @@ -1255,6 +1295,7 @@ def fill_parser(self, parser): def run(self, parsed_args): """Run the command.""" + _emit_charmlibs_deprecation_warning() lib_name = parsed_args.name valid_all_chars = set(string.ascii_lowercase + string.digits + "_") valid_first_char = string.ascii_lowercase @@ -1349,6 +1390,7 @@ def fill_parser(self, parser): def run(self, parsed_args): """Run the command.""" + _emit_charmlibs_deprecation_warning() charm_name = ( self._services.get("project").get().name or utils.get_name_from_yaml() ) @@ -1515,6 +1557,7 @@ def fill_parser(self, parser): def run(self, parsed_args: argparse.Namespace) -> None: """Run the command.""" + _emit_charmlibs_deprecation_warning() if parsed_args.library: local_libs_data = [utils.get_lib_info(full_name=parsed_args.library)] else: @@ -1666,6 +1709,7 @@ class FetchLibs(CharmcraftCommand): def run(self, parsed_args: argparse.Namespace) -> None: """Fetch libraries.""" + _emit_charmlibs_deprecation_warning() store = cast("StoreService", self._services.get("store")) project = cast("CharmcraftProject", self._services.get("project").get()) charm_libs = project.charm_libs @@ -1774,6 +1818,7 @@ def fill_parser(self, parser): def run(self, parsed_args): """Run the command.""" + _emit_charmlibs_deprecation_warning() if parsed_args.name: charm_name = parsed_args.name else: diff --git a/charmcraft/application/main.py b/charmcraft/application/main.py index 7727d960d..63e634759 100644 --- a/charmcraft/application/main.py +++ b/charmcraft/application/main.py @@ -18,6 +18,7 @@ from __future__ import annotations import datetime +import os from typing import Any import craft_application @@ -131,9 +132,20 @@ def _get_app_plugins(self) -> dict[str, PluginType]: except ProjectFileMissingError: return plugins bases = {str(build_info.build_base) for build_info in full_build_plan} - if any(base not in const.CHARM_PLUGIN_BASES for base in bases): + effective_charm_bases = const.CHARM_PLUGIN_BASES + if os.getenv(const.EXPERIMENTAL_EXTENSIONS_ENV_VAR): + effective_charm_bases = ( + effective_charm_bases | const.CHARM_PLUGIN_EXPERIMENTAL_BASES + ) + if any(base not in effective_charm_bases for base in bases): plugins.pop("charm", None) - if any(base not in const.REACTIVE_PLUGIN_BASES for base in bases): + effective_reactive_bases = const.REACTIVE_PLUGIN_BASES + experimental_env = os.getenv(const.EXPERIMENTAL_EXTENSIONS_ENV_VAR) + if experimental_env and util.strtobool(str(experimental_env)): + effective_reactive_bases = ( + effective_reactive_bases | const.REACTIVE_PLUGIN_EXPERIMENTAL_BASES + ) + if any(base not in effective_reactive_bases for base in bases): plugins.pop("reactive", None) return plugins diff --git a/charmcraft/const.py b/charmcraft/const.py index f02a87261..550463a03 100644 --- a/charmcraft/const.py +++ b/charmcraft/const.py @@ -32,6 +32,7 @@ STORE_API_ENV_VAR = "CHARMCRAFT_STORE_API_URL" STORE_STORAGE_ENV_VAR = "CHARMCRAFT_UPLOAD_URL" STORE_REGISTRY_ENV_VAR = "CHARMCRAFT_REGISTRY_URL" +STORE_SSO_URL_ENV_VAR = "CHARMCRAFT_STORE_SSO_URL" # These are only for use within the managed environment MANAGED_MODE_ENV_VAR = "CHARMCRAFT_MANAGED_MODE" # endregion @@ -68,15 +69,28 @@ "ubuntu@24.04", "ubuntu@24.10", "ubuntu@25.04", - "ubuntu@26.10", + ) +) + +CHARM_PLUGIN_EXPERIMENTAL_BASES = ( + frozenset( # Experimental bases with the 'charm' plugin. + ( + "ubuntu@26.04", + "ubuntu@26.10", + ) ) ) REACTIVE_PLUGIN_BASES = frozenset( # Bases with the 'reactive' plugin. - ( - *CHARM_PLUGIN_BASES, - "ubuntu@26.04", - "ubuntu@26.10", + (*CHARM_PLUGIN_BASES,) +) + +REACTIVE_PLUGIN_EXPERIMENTAL_BASES = ( + frozenset( # Experimental bases with the 'reactive' plugin. + ( + "ubuntu@26.04", + "ubuntu@26.10", + ) ) ) diff --git a/charmcraft/env.py b/charmcraft/env.py index 922308677..68c3ce862 100644 --- a/charmcraft/env.py +++ b/charmcraft/env.py @@ -77,6 +77,7 @@ class CharmhubConfig: api_url: str = "https://api.charmhub.io" storage_url: str = "https://storage.snapcraftcontent.com" registry_url: str = "https://registry.jujucharms.com" + login_url: str = "https://login.ubuntu.com" DEFAULT_CHARMHUB_CONFIG = CharmhubConfig() @@ -91,6 +92,12 @@ def get_store_config() -> CharmhubConfig: registry_url = os.getenv( const.STORE_REGISTRY_ENV_VAR, DEFAULT_CHARMHUB_CONFIG.registry_url ) + login_url = os.getenv( + const.STORE_SSO_URL_ENV_VAR, DEFAULT_CHARMHUB_CONFIG.login_url + ) return CharmhubConfig( - api_url=api_url, storage_url=storage_url, registry_url=registry_url + api_url=api_url, + storage_url=storage_url, + registry_url=registry_url, + login_url=login_url, ) diff --git a/charmcraft/extensions/__init__.py b/charmcraft/extensions/__init__.py index dac921242..9e0f611c6 100644 --- a/charmcraft/extensions/__init__.py +++ b/charmcraft/extensions/__init__.py @@ -18,12 +18,12 @@ from charmcraft.extensions._utils import apply_extensions from charmcraft.extensions.app import ( - DjangoFramework, - FastAPIFramework, - FlaskFramework, - GoFramework, - ExpressJSFramework, - SpringBootFramework, + DjangoFrameworkFactory, + ExpressJSFrameworkFactory, + FastAPIFrameworkFactory, + FlaskFrameworkFactory, + GoFrameworkFactory, + SpringBootFrameworkFactory, ) from charmcraft.extensions.extension import Extension from charmcraft.extensions.registry import ( @@ -35,9 +35,7 @@ ) __all__ = [ - "DjangoFramework", "Extension", - "FlaskFramework", "get_extension_class", "get_extension_names", "get_extensions", @@ -46,9 +44,11 @@ "unregister", ] -register("flask-framework", FlaskFramework) -register("django-framework", DjangoFramework) -register("go-framework", GoFramework) -register("fastapi-framework", FastAPIFramework) -register("expressjs-framework", ExpressJSFramework) -register("spring-boot-framework", SpringBootFramework) +# Factory instances are registered in place of Extension subclasses for the +# 12-factor app extensions only, until craft-wide extensions land (CRAFT-5152). +register("flask-framework", FlaskFrameworkFactory) # type: ignore +register("django-framework", DjangoFrameworkFactory) # type: ignore +register("go-framework", GoFrameworkFactory) # type: ignore +register("fastapi-framework", FastAPIFrameworkFactory) # type: ignore +register("expressjs-framework", ExpressJSFrameworkFactory) # type: ignore +register("spring-boot-framework", SpringBootFrameworkFactory) # type: ignore diff --git a/charmcraft/extensions/app.py b/charmcraft/extensions/app.py index ff15be707..fadb3c2bb 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -20,10 +20,11 @@ from pathlib import Path from typing import Any +import yaml from overrides import override from ..errors import ExtensionError -from .extension import Extension +from .extension import Extension, SinglePlatformExtension, get_project_bases APP_PORT_OPTION = { "app-port": { @@ -71,9 +72,63 @@ } COS_SUBDIRS = {"grafana_dashboards", "loki_alert_rules", "prometheus_alert_rules"} +PAAS_CONFIG_FILE = "paas-config.yaml" +JSON_LOGGING_SUPPORTED_FRAMEWORKS = {"fastapi", "flask", "django"} -class _AppBase(Extension): +class _FrameworkFactory: + """Route to a V1 or V2 extension class based on the project's target bases. + + Instances are callable and expose get_supported_bases and is_experimental + so they can be registered and introspected like an Extension subclass. + + The V2 classes will always supersedes V1 if the base is supported by V2 class. + """ + + def __init__(self, v1_cls: type[Extension], v2_cls: type[Extension]) -> None: + """Store the V1 and V2 extension classes. + + :param v1_cls: the V1 extension class. + :param v2_cls: the V2 extension class. + """ + self._v1_cls = v1_cls + self._v2_cls = v2_cls + + def __call__(self, *, project_root: Path, yaml_data: dict[str, Any]) -> Extension: + """Route to V1 or V2 based on project bases. + + :param project_root: the project root directory. + :param yaml_data: the raw yaml data. + :return: an Extension instance from the appropriate version. + """ + bases = get_project_bases(yaml_data) + if any(base in self._v1_cls.get_supported_bases() for base in bases): + return self._v1_cls(project_root=project_root, yaml_data=yaml_data) + return self._v2_cls(project_root=project_root, yaml_data=yaml_data) + + def get_supported_bases(self) -> list[tuple[str, str]]: + """Return merged supported bases from both V1 and V2, deduped and ordered. + + :return: list of supported (distribution, series) tuples. + """ + return list( + dict.fromkeys( + self._v1_cls.get_supported_bases() + self._v2_cls.get_supported_bases() + ) + ) + + def is_experimental(self, base: tuple[str, str] | None) -> bool: + """Check if experimental, delegating to the class that supports the base. + + :param base: the target base tuple or None. + :return: True if the base is experimental, False otherwise. + """ + if base in self._v1_cls.get_supported_bases(): + return self._v1_cls.is_experimental(base) + return self._v2_cls.is_experimental(base) + + +class _AppBase(SinglePlatformExtension): """A base class for 12-factor applications.""" _CHARM_LIBS = [ @@ -97,13 +152,13 @@ class _AppBase(Extension): @override def get_supported_bases() -> list[tuple[str, str]]: """Return supported bases.""" - return [("ubuntu", "22.04")] + return [("ubuntu", "22.04"), ("ubuntu", "24.04")] @staticmethod @override - def is_experimental(base: tuple[str, ...] | None) -> bool: # noqa: ARG004 + def is_experimental(base: tuple[str, str] | None) -> bool: # noqa: ARG004 """Check if the extension is in an experimental state.""" - return True + return False framework: str actions: dict = { @@ -194,6 +249,34 @@ def _check_input(self) -> None: "Non-optional configuration options can not have default values.\n" f"Please either remove the default value or set optional field to true or remove it for the {', '.join(invalid_non_optionals)} configuration option(s)." ) + self._check_paas_config() + + def _check_paas_config(self) -> None: + """Validate ``paas-config.yaml`` syntax and framework logging compatibility.""" + config_path = self.project_root / PAAS_CONFIG_FILE + if not config_path.exists(): + return + + try: + parsed = yaml.safe_load(config_path.read_text(encoding="utf-8")) + except yaml.YAMLError as exc: + raise ExtensionError(f"invalid YAML in {PAAS_CONFIG_FILE}: {exc}") from exc + + if parsed is None: + return + if not isinstance(parsed, dict): + raise ExtensionError(f"{PAAS_CONFIG_FILE} must contain a top-level mapping") + + framework_logging_format = parsed.get("framework_logging_format") + if ( + isinstance(framework_logging_format, str) + and framework_logging_format.lower() == "json" + and self.framework not in JSON_LOGGING_SUPPORTED_FRAMEWORKS + ): + raise ExtensionError( + f"framework_logging_format: json in {PAAS_CONFIG_FILE} is not supported " + f"for '{self.framework}-framework'" + ) def _validate_cos_custom_dir(self) -> None: """Validate the custom COS directory if present.""" @@ -325,6 +408,61 @@ def get_image_name(self) -> str: return f"{self.framework}-app-image" +class _AppBaseV2(_AppBase): + """V2 base class for 12-factor applications using uv.""" + + @staticmethod + @override + def get_supported_bases() -> list[tuple[str, str]]: + """Return supported bases.""" + return [("ubuntu", "26.04")] + + @override + def _get_root_snippet(self) -> dict[str, Any]: + """Return the root snippet to be merged into the user charmcraft.yaml.""" + snippet = super()._get_root_snippet() + snippet["parts"] = { + "charm": { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + }, + **self.get_config_part(), + } + return snippet + + def get_config_part(self) -> dict[str, Any]: + """Get config part if paas-config.yaml is present.""" + config_file = Path(self.project_root) / "paas-config.yaml" + if not config_file.is_file(): + return {} + return { + "config": { + "plugin": "dump", + "source": ".", + "stage": ["paas-config.yaml"], + } + } + + @override + def get_image_name(self) -> str: + """Return name of the app image.""" + return "app-image" + + @override + def get_container_name(self) -> str: + """Return name of the container for the app image.""" + return "app" + + @staticmethod + @override + def is_experimental(base: tuple[str, str] | None) -> bool: # noqa: ARG004 + """Check if the extension is_experimental is always True for V2.""" + return True + + GUNICORN_WEBSERVER_OPTIONS = { "webserver-keepalive": { "type": "int", @@ -349,7 +487,7 @@ def get_image_name(self) -> str: } -class FlaskFramework(_AppBase): +class FlaskFrameworkV1(_AppBase): """Extension for 12-factor Flask applications.""" framework = "flask" @@ -395,12 +533,22 @@ class FlaskFramework(_AppBase): @staticmethod @override - def is_experimental(base: tuple[str, ...] | None) -> bool: # noqa: ARG004 - """Check if the extension is in an experimental state.""" - return False + def get_supported_bases() -> list[tuple[str, str]]: + """Return supported bases.""" + return [("ubuntu", "22.04"), ("ubuntu", "24.04")] + + +class FlaskFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor Flask applications.""" + + framework = "flask" + options = FlaskFrameworkV1.options + +FlaskFrameworkFactory = _FrameworkFactory(FlaskFrameworkV1, FlaskFrameworkV2) -class DjangoFramework(_AppBase): + +class DjangoFrameworkV1(_AppBase): """Extension for 12-factor Django applications.""" framework = "django" @@ -438,12 +586,29 @@ class DjangoFramework(_AppBase): @staticmethod @override - def is_experimental(base: tuple[str, ...] | None) -> bool: # noqa: ARG004 + def get_supported_bases() -> list[tuple[str, str]]: + """Return supported bases.""" + return [("ubuntu", "22.04"), ("ubuntu", "24.04")] + + @staticmethod + @override + def is_experimental(base: tuple[str, str] | None) -> bool: # noqa: ARG004 """Check if the extension is in an experimental state.""" return False -class GoFramework(_AppBase): +class DjangoFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor Django applications.""" + + framework = "django" + actions = {**DjangoFrameworkV1.actions} + options = DjangoFrameworkV1.options + + +DjangoFrameworkFactory = _FrameworkFactory(DjangoFrameworkV1, DjangoFrameworkV2) + + +class GoFrameworkV1(_AppBase): """Extension for 12-factor Go applications.""" framework = "go" @@ -470,7 +635,21 @@ def get_container_name(self) -> str: return "app" -class FastAPIFramework(_AppBase): +class GoFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor Go applications.""" + + framework = "go" + options = { + **APP_PORT_OPTION, + **METRICS_OPTIONS, + **SECRET_OPTIONS, + } + + +GoFrameworkFactory = _FrameworkFactory(GoFrameworkV1, GoFrameworkV2) + + +class FastAPIFrameworkV1(_AppBase): """Extension for 12-factor FastAPI applications.""" framework = "fastapi" @@ -511,7 +690,17 @@ def get_container_name(self) -> str: return "app" -class ExpressJSFramework(_AppBase): +class FastAPIFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor FastAPI applications.""" + + framework = "fastapi" + options = FastAPIFrameworkV1.options + + +FastAPIFrameworkFactory = _FrameworkFactory(FastAPIFrameworkV1, FastAPIFrameworkV2) + + +class ExpressJSFrameworkV1(_AppBase): """Extension for 12-factor ExpressJS applications.""" framework = "expressjs" @@ -538,7 +727,19 @@ def get_container_name(self) -> str: return "app" -class SpringBootFramework(_AppBase): +class ExpressJSFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor ExpressJS applications.""" + + framework = "expressjs" + options = ExpressJSFrameworkV1.options + + +ExpressJSFrameworkFactory = _FrameworkFactory( + ExpressJSFrameworkV1, ExpressJSFrameworkV2 +) + + +class SpringBootFrameworkV1(_AppBase): """Extension for 12-factor Spring Boot applications.""" framework = "spring-boot" @@ -583,12 +784,6 @@ def get_supported_bases() -> list[tuple[str, str]]: """Return supported bases.""" return [("ubuntu", "24.04")] - @staticmethod - @override - def is_experimental(base: tuple[str, ...] | None) -> bool: # noqa: ARG004 - """Check if the extension is in an experimental state.""" - return False - @override def get_image_name(self) -> str: """Return name of the app image.""" @@ -598,3 +793,16 @@ def get_image_name(self) -> str: def get_container_name(self) -> str: """Return name of the container for the app image.""" return "app" + + +class SpringBootFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor Spring Boot applications.""" + + framework = "spring-boot" + options = SpringBootFrameworkV1.options + endpoint_dynamic_options = SpringBootFrameworkV1.endpoint_dynamic_options + + +SpringBootFrameworkFactory = _FrameworkFactory( + SpringBootFrameworkV1, SpringBootFrameworkV2 +) diff --git a/charmcraft/extensions/extension.py b/charmcraft/extensions/extension.py index 7e4a96355..98267f609 100644 --- a/charmcraft/extensions/extension.py +++ b/charmcraft/extensions/extension.py @@ -30,6 +30,60 @@ from charmcraft import const, errors +def get_project_bases(yaml_data: dict[str, Any]) -> set[tuple[str, str]]: + """Extract and normalize all bases used in the project. + + Handles the `base` field, `platforms` with labels and `build-for`, + and legacy `bases` in both short and long formats. + + :param yaml_data: the raw yaml data. + :return: a set of normalized (distribution, series) tuples. + """ + bases: set[tuple[str, str]] = set() + + if base_str := yaml_data.get("base"): + if parsed := craft_platforms.parse_base_and_name(base_str)[0]: + bases.add((parsed.distribution, parsed.series)) + else: + name, _, channel = base_str.partition("@") + bases.add((name, channel)) + + if platforms := yaml_data.get("platforms", {}): + for label, data in platforms.items(): + if base := craft_platforms.parse_base_and_name(label)[0]: + bases.add((base.distribution, base.series)) + elif data and (build_for := data.get("build-for")): + build_for_items = ( + build_for if isinstance(build_for, list) else [build_for] + ) + for item in build_for_items: + if base := craft_platforms.parse_base_and_architecture(item)[0]: + bases.add((base.distribution, base.series)) + + if legacy_bases := yaml_data.get("bases"): + for b in legacy_bases: + # Handle both short form ({name, channel}) and long form ({build-on: [...]}) + if "build-on" in b: + for build_on in b.get("build-on", []): + name = build_on.get("name") + channel = build_on.get("channel") + base_str = f"{name}@{channel}" + if parsed := craft_platforms.parse_base_and_name(base_str)[0]: + bases.add((parsed.distribution, parsed.series)) + else: + bases.add((name, channel)) + elif "name" in b and "channel" in b: + name = b["name"] + channel = b["channel"] + base_str = f"{name}@{channel}" + if parsed := craft_platforms.parse_base_and_name(base_str)[0]: + bases.add((parsed.distribution, parsed.series)) + else: + bases.add((name, channel)) + + return bases + + class Extension(abc.ABC): """Extension is the class from which all extensions inherit. @@ -73,49 +127,7 @@ def get_parts_snippet(self) -> dict[str, Any]: def _get_project_bases(self) -> set[tuple[str, str]]: """Extract and normalize all bases used in the project.""" - bases: set[tuple[str, str]] = set() - - if base_str := self.yaml_data.get("base"): - if parsed := craft_platforms.parse_base_and_name(base_str)[0]: - bases.add((parsed.distribution, parsed.series)) - else: - name, _, channel = base_str.partition("@") - bases.add((name, channel)) - - if platforms := self.yaml_data.get("platforms", {}): - for label, data in platforms.items(): - if base := craft_platforms.parse_base_and_name(label)[0]: - bases.add((base.distribution, base.series)) - elif data and (build_for := data.get("build-for")): - build_for_items = ( - build_for if isinstance(build_for, list) else [build_for] - ) - for item in build_for_items: - if base := craft_platforms.parse_base_and_architecture(item)[0]: - bases.add((base.distribution, base.series)) - - if legacy_bases := self.yaml_data.get("bases"): - for b in legacy_bases: - # Handle both short form ({name, channel}) and long form ({build-on: [...]}) - if "build-on" in b: - for build_on in b.get("build-on", []): - name = build_on.get("name") - channel = build_on.get("channel") - base_str = f"{name}@{channel}" - if parsed := craft_platforms.parse_base_and_name(base_str)[0]: - bases.add((parsed.distribution, parsed.series)) - else: - bases.add((name, channel)) - elif "name" in b and "channel" in b: - name = b["name"] - channel = b["channel"] - base_str = f"{name}@{channel}" - if parsed := craft_platforms.parse_base_and_name(base_str)[0]: - bases.add((parsed.distribution, parsed.series)) - else: - bases.add((name, channel)) - - return bases + return get_project_bases(self.yaml_data) def validate(self, extension_name: str): """Validate that the extension can be used with the current project. @@ -173,8 +185,6 @@ class SinglePlatformExtension(Extension): @override def validate(self, extension_name: str) -> None: """Validate that the extension is only used with a single base.""" - super().validate(extension_name) - bases = self._get_project_bases() if len(bases) > 1: bases_str = ", ".join(f"{n}@{c}" for n, c in sorted(bases)) @@ -182,6 +192,8 @@ def validate(self, extension_name: str) -> None: f"Extension does not support multiple bases: {bases_str}" ) + super().validate(extension_name) + def get_extensions_data_dir() -> Path: """Return the path to the extension data directory.""" diff --git a/charmcraft/models/project.py b/charmcraft/models/project.py index 1e4f485ac..4c135c1f4 100644 --- a/charmcraft/models/project.py +++ b/charmcraft/models/project.py @@ -17,6 +17,7 @@ import abc import datetime +import os import pathlib import re import textwrap @@ -965,7 +966,12 @@ def _validate_removed_questing_plugins( ) } - if invalid_bases := build_bases - const.REACTIVE_PLUGIN_BASES: + effective_reactive_bases = const.REACTIVE_PLUGIN_BASES + if os.getenv(const.EXPERIMENTAL_EXTENSIONS_ENV_VAR): + effective_reactive_bases = ( + effective_reactive_bases | const.REACTIVE_PLUGIN_EXPERIMENTAL_BASES + ) + if invalid_bases := build_bases - effective_reactive_bases: if len(invalid_bases) == 1: raise ValueError( f"Cannot use 'charm' or 'reactive' plugins with base {invalid_bases.pop()!r}" @@ -976,7 +982,12 @@ def _validate_removed_questing_plugins( ) if "charm" in legacy_plugins: - if invalid_bases := build_bases - const.CHARM_PLUGIN_BASES: + effective_charm_bases = const.CHARM_PLUGIN_BASES + if os.getenv(const.EXPERIMENTAL_EXTENSIONS_ENV_VAR): + effective_charm_bases = ( + effective_charm_bases | const.CHARM_PLUGIN_EXPERIMENTAL_BASES + ) + if invalid_bases := build_bases - effective_charm_bases: if len(invalid_bases) == 1: raise ValueError( f"Cannot use 'charm' plugin with base {invalid_bases.pop()!r}" diff --git a/charmcraft/services/store.py b/charmcraft/services/store.py index 40b81f0e9..1edda9db8 100644 --- a/charmcraft/services/store.py +++ b/charmcraft/services/store.py @@ -17,6 +17,7 @@ from __future__ import annotations +import os import platform from collections.abc import Collection, Mapping, Sequence from typing import Any, cast @@ -28,6 +29,7 @@ from craft_cli import emit from craft_store import models, publisher from craft_store.errors import StoreServerError +from craft_store.login import UbuntuOneLogin from overrides import override from charmcraft import const, env, errors, store @@ -47,10 +49,11 @@ class BaseStoreService(craft_application.AppService): This service should be easily adjustable for craft-application. """ - ClientClass: type[craft_store.StoreClient] = craft_store.StoreClient - client: craft_store.StoreClient - _endpoints: craft_store.endpoints.Endpoints = craft_store.endpoints.CHARMHUB + _namespace: str = "charm" + _auth: craft_store.Auth + _publisher: craft_store.PublisherGateway _environment_auth: str = const.ALTERNATE_AUTH_ENV_VAR + _ephemeral: bool = False @property def _user_agent(self) -> str: @@ -82,56 +85,69 @@ def _base_url(self) -> str: def _storage_url(self) -> str: return env.get_store_config().storage_url + @property + def _login_url(self) -> str: + """Get the Ubuntu One SSO login URL.""" + return env.get_store_config().login_url + + def _setup_auth(self, *, ephemeral: bool = False) -> craft_store.Auth: + """Create and return a new Auth object.""" + return craft_store.Auth( + application_name=self._app.name, + host=str(parse.urlparse(self._base_url).netloc), + environment_auth=self._environment_auth, + ephemeral=ephemeral, + ) + def setup(self) -> None: """Set up the store service.""" super().setup() - try: - self.client = self.ClientClass( - application_name=self._app.name, - base_url=self._base_url, - storage_base_url=self._storage_url, - endpoints=self._endpoints, - environment_auth=self._environment_auth, - user_agent=self._user_agent, - ) + self._auth = self._setup_auth() except craft_store.errors.NoKeyringError: emit.progress( "WARNING: Cannot get a keyring. Every store interaction that requires " "authentication will require you to log in again.", permanent=True, ) - self.client = self.ClientClass( - application_name=self._app.name, - base_url=self._base_url, - storage_base_url=self._storage_url, - endpoints=self._endpoints, - environment_auth=self._environment_auth, - user_agent=self._user_agent, - ephemeral=True, - ) + self._ephemeral = True + self._auth = self._setup_auth(ephemeral=True) + self._publisher = craft_store.PublisherGateway( + base_url=self._base_url, + namespace=self._namespace, + auth=craft_store.UbuntuOneAuth( + auth=self._auth, + api_base_url=self._base_url, + client_description=self._get_description(), + ), + ) - def _get_description(self, description: str | None = None) -> str: - """Return the given description or a default one.""" - if description is None: - return f"{self._app.name}@{platform.node()}" - return description + def _get_description(self) -> str: + """Return a description for identifying this client.""" + return f"{self._app.name}@{platform.node()}" def login( self, + email: str, + password: str, + *, + otp: str | None = None, permissions: Sequence[str] = AUTH_DEFAULT_PERMISSIONS, - description: str | None = None, ttl: int = AUTH_DEFAULT_TTL, - packages: Sequence[craft_store.endpoints.Package] | None = None, + packages: Collection[Mapping[str, str]] | None = None, channels: Sequence[str] | None = None, - ) -> str: + ) -> None: """Login to the store.""" - description = self._get_description(description) - try: - return self.client.login( + self._auth.ensure_no_credentials() + UbuntuOneLogin.login_with( + email=email, + password=password, + otp=otp, + base_url=self._base_url, + login_url=self._login_url, + application_name=self._app.name, permissions=permissions, - description=description, ttl=ttl, packages=packages, channels=channels, @@ -145,18 +161,25 @@ def login( def logout(self) -> None: """Log out of the store.""" - self.client.logout() + self._auth.del_credentials() + + def whoami(self) -> dict[str, Any]: + """Return full whoami info from the store.""" + return self._publisher.whoami() def get_account_info(self): """Get the account details of the logged-in account.""" - return self.client.whoami()["account"] + return self.whoami()["account"] def get_credentials( self, + email: str, + password: str, + *, + otp: str | None = None, permissions: Sequence[str] = AUTH_DEFAULT_PERMISSIONS, - description: str | None = None, ttl: int = AUTH_DEFAULT_TTL, - packages: Sequence[craft_store.endpoints.Package] | None = None, + packages: Collection[Mapping[str, str]] | None = None, channels: Sequence[str] | None = None, ) -> str: """Create a fresh set of login credentials for the store. @@ -164,54 +187,99 @@ def get_credentials( This logs in independent of any credentials currently stored for the application and returns the resulting macaroon as a string. """ - description = self._get_description(description) - - store = craft_store.StoreClient( + ephemeral_auth = craft_store.Auth( application_name=self._app.name, - base_url=self._base_url, - storage_base_url=self._storage_url, - endpoints=self._endpoints, - environment_auth=None, - user_agent=self._user_agent, + host=str(parse.urlparse(self._base_url).netloc), ephemeral=True, + environment_auth=None, ) - - return store.login( + UbuntuOneLogin.login_with( + email=email, + password=password, + otp=otp, + base_url=self._base_url, + login_url=self._login_url, + application_name=self._app.name, + store_auth=ephemeral_auth, permissions=permissions, - description=description, ttl=ttl, packages=packages, channels=channels, ) + # Exchange the raw macaroons for a store token. + craft_store.UbuntuOneAuth( + auth=ephemeral_auth, + api_base_url=self._base_url, + client_description=self._get_description(), + ).get_token_from_keyring() + + raw_creds = ephemeral_auth.get_credentials() + return ephemeral_auth.encode_credentials(raw_creds) + class StoreService(BaseStoreService): """A Store service specifically for Charmcraft.""" - ClientClass = store.Client - client: store.Client # pyright: ignore[reportIncompatibleVariableOverride] + client: store.Client anonymous_client: store.AnonymousClient - _publisher: craft_store.PublisherGateway @override def setup(self) -> None: """Set up the store service.""" super().setup() - self.anonymous_client = store.AnonymousClient( + self.client = store.Client( api_base_url=self._base_url, storage_base_url=self._storage_url, - ) - self._auth = craft_store.Auth( application_name=self._app.name, - host=str(parse.urlparse(self._base_url).hostname), + auth_url=self._login_url, environment_auth=self._environment_auth, + user_agent=self._user_agent, + ephemeral=self._ephemeral, ) - self._publisher = craft_store.PublisherGateway( - base_url=self._base_url, - namespace="charm", - auth=self._auth, + self.anonymous_client = store.AnonymousClient( + api_base_url=self._base_url, + storage_base_url=self._storage_url, ) + @override + def login( + self, + email: str, + password: str, + *, + otp: str | None = None, + permissions: Sequence[str] = AUTH_DEFAULT_PERMISSIONS, + ttl: int = AUTH_DEFAULT_TTL, + packages: Collection[Mapping[str, str]] | None = None, + channels: Sequence[str] | None = None, + ) -> None: + """Login to the store using Ubuntu One SSO credentials.""" + if os.getenv(self._environment_auth): + raise errors.CraftError( + f"Cannot login when using alternative auth through " + f"{self._environment_auth} environment variable." + ) + super().login( + email, + password, + otp=otp, + permissions=permissions, + ttl=ttl, + packages=packages, + channels=channels, + ) + + @override + def logout(self) -> None: + """Log out of the store.""" + if os.getenv(self._environment_auth): + raise errors.CraftError( + f"Cannot logout when using alternative auth through " + f"{self._environment_auth} environment variable." + ) + super().logout() + def get_package_metadata(self, name: str) -> publisher.RegisteredName: """Get the metadata for a package. diff --git a/charmcraft/store/client.py b/charmcraft/store/client.py index a779ab562..19b5427b1 100644 --- a/charmcraft/store/client.py +++ b/charmcraft/store/client.py @@ -156,8 +156,8 @@ def list_resource_revisions(self, *args: Any, **kwargs: Any) -> NoReturn: raise NotImplementedError("This operation requires authentication.") -class Client(craft_store.StoreClient): - """Lightweight layer above StoreClient.""" +class Client(craft_store.UbuntuOneStoreClient): + """Lightweight layer above UbuntuOneStoreClient.""" def __init__( self, @@ -167,6 +167,7 @@ def __init__( application_name: str = "charmcraft", *, base_url: str = "", + auth_url: str = "https://login.ubuntu.com", endpoints: endpoints.Endpoints = endpoints.CHARMHUB, environment_auth: str = const.ALTERNATE_AUTH_ENV_VAR, user_agent: str = build_user_agent(), @@ -187,6 +188,7 @@ def __init__( super().__init__( base_url=api_base_url, storage_base_url=storage_base_url, + auth_url=auth_url, endpoints=endpoints, application_name=application_name, user_agent=user_agent, @@ -194,6 +196,9 @@ def __init__( ephemeral=ephemeral, ) + def _get_authorization_header(self) -> str: + return f"Macaroon {self._auth.get_credentials()}" + def login(self, *args, **kwargs): """Intercept regular login functionality to forbid it when using alternate auth.""" if os.getenv(const.ALTERNATE_AUTH_ENV_VAR) is not None: diff --git a/charmcraft/templates/init-django-framework-26.04/.gitignore.j2 b/charmcraft/templates/init-django-framework-26.04/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-django-framework-26.04/charmcraft.yaml.j2 b/charmcraft/templates/init-django-framework-26.04/charmcraft.yaml.j2 new file mode 100644 index 000000000..a08b1cb84 --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/charmcraft.yaml.j2 @@ -0,0 +1,81 @@ +# This file configures Charmcraft. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com + +name: {{ name }} + +type: charm + +base: ubuntu@26.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the Django application. + +# (Required) +description: | + A comprehensive overview of your Django application. + +extensions: + - django-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# valkey: +# interface: valkey_client +# optional: true +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1 +# tracing: +# interface: tracing +# optional: true +# limit: 1 +# smtp: +# interface: smtp +# optional: false +# limit: 1 +# openfga: +# interface: openfga +# optional: false +# limit: 1 +# http-proxy: +# interface: http_proxy +# optional: true +# limit: 1 + diff --git a/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 new file mode 100644 index 000000000..a3c32be91 --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,89 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[project] +name = "{{ name }}" +version = "0.0.1" +requires-python = ">=3.10" + +# Dependencies of the charm code +# You should list the dependencies of the code in src/, including any charm libraries from PyPI. +# We recommend using uv to maintain this list. For example, 'uv add charmlibs-pathops'. +# If your code uses any libraries from Charmhub, don't list those libraries here. Instead, add a +# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries, +# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here. +dependencies = [ + "dpcharmlibs-interfaces==1.0.2", + "jinja2==3.1.6", + "jsonschema==4.26", + "ops==3.7.1", + "paas-charm>=1.0", +] +[dependency-groups] +# PYDEPS from libraries that the charm uses. +charmlibs-pydeps = [ + "cosl==1.9.1", + "pydantic==2.13.3", +] +# Dependencies of linting and static type checks +lint = [ + "ruff", + "codespell", + "pyright", +] +# Dependencies of unit tests +unit = [ + "coverage[toml]", + "ops[testing]", + "pytest", +] +# Dependencies of integration tests +integration = [ + "jubilant>=1.8,<2", + "pytest", + "pytest-jubilant>=2.0.1,<3", + "PyYAML", +] + + +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +lint.select = ["E", "W", "F", "C", "N", "D", "I001"] +lint.ignore = [ + "D105", + "D107", + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-exclude = ["__pycache__", "*.egg_info"] +lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] diff --git a/charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 b/charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 new file mode 100755 index 000000000..4a994d611 --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""Django Charm entrypoint.""" + +import logging +import typing + +import ops +import paas_charm.django + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_charm.django.Charm): + """Django Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main({{ class_name }}) diff --git a/charmcraft/templates/init-django-framework-26.04/tox.ini.j2 b/charmcraft/templates/init-django-framework-26.04/tox.ini.j2 new file mode 100644 index 000000000..17bbc92fb --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/tox.ini.j2 @@ -0,0 +1,76 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +all_path = {[vars]src_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + ruff +commands = + ruff format {[vars]all_path} + ruff check --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards, and static checks +runner = uv-venv-lock-runner +dependency_groups = + lint + unit + integration +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + pyright {posargs} + +[testenv:unit] +description = Run unit tests +runner = uv-venv-lock-runner +dependency_groups = + unit +commands = + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +runner = uv-venv-lock-runner +dependency_groups = + integration +pass_env = + # The integration tests don't pack the charm. If CHARM_PATH is set, the tests deploy the + # specified .charm file. Otherwise, the tests look for a .charm file in the project dir. + CHARM_PATH +commands = + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} diff --git a/charmcraft/templates/init-django-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-django-framework/charmcraft.yaml.j2 index 5bccbe6ee..de33834a2 100644 --- a/charmcraft/templates/init-django-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-django-framework/charmcraft.yaml.j2 @@ -1,6 +1,6 @@ # This file configures Charmcraft. -# See https://juju.is/docs/sdk/charmcraft-config for guidance. -# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com name: {{ name }} diff --git a/charmcraft/templates/init-django-framework/tox.ini.j2 b/charmcraft/templates/init-django-framework/tox.ini.j2 index bd15d819c..bacc920a4 100644 --- a/charmcraft/templates/init-django-framework/tox.ini.j2 +++ b/charmcraft/templates/init-django-framework/tox.ini.j2 @@ -9,7 +9,7 @@ min_version = 4.0.0 [vars] src_path = {tox_root}/src -;tests_path = {tox_root}/tests +tests_path = {tox_root}/tests all_path = {[vars]src_path} [testenv] diff --git a/charmcraft/templates/init-expressjs-framework-26.04/.gitignore.j2 b/charmcraft/templates/init-expressjs-framework-26.04/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-expressjs-framework-26.04/charmcraft.yaml.j2 b/charmcraft/templates/init-expressjs-framework-26.04/charmcraft.yaml.j2 new file mode 100644 index 000000000..0f83827e8 --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/charmcraft.yaml.j2 @@ -0,0 +1,80 @@ +# This file configures Charmcraft. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com + +name: {{ name }} + +type: charm + +base: ubuntu@26.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the ExpressJS application. + +# (Required) +description: | + A comprehensive overview of your ExpressJS application. + +extensions: + - expressjs-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# valkey: +# interface: valkey_client +# optional: true +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1 +# tracing: +# interface: tracing +# optional: true +# limit: 1 +# smtp: +# interface: smtp +# optional: false +# limit: 1 +# openfga: +# interface: openfga +# optional: false +# limit: 1 +# http-proxy: +# interface: http_proxy +# optional: true +# limit: 1 diff --git a/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 new file mode 100644 index 000000000..a3c32be91 --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,89 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[project] +name = "{{ name }}" +version = "0.0.1" +requires-python = ">=3.10" + +# Dependencies of the charm code +# You should list the dependencies of the code in src/, including any charm libraries from PyPI. +# We recommend using uv to maintain this list. For example, 'uv add charmlibs-pathops'. +# If your code uses any libraries from Charmhub, don't list those libraries here. Instead, add a +# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries, +# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here. +dependencies = [ + "dpcharmlibs-interfaces==1.0.2", + "jinja2==3.1.6", + "jsonschema==4.26", + "ops==3.7.1", + "paas-charm>=1.0", +] +[dependency-groups] +# PYDEPS from libraries that the charm uses. +charmlibs-pydeps = [ + "cosl==1.9.1", + "pydantic==2.13.3", +] +# Dependencies of linting and static type checks +lint = [ + "ruff", + "codespell", + "pyright", +] +# Dependencies of unit tests +unit = [ + "coverage[toml]", + "ops[testing]", + "pytest", +] +# Dependencies of integration tests +integration = [ + "jubilant>=1.8,<2", + "pytest", + "pytest-jubilant>=2.0.1,<3", + "PyYAML", +] + + +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +lint.select = ["E", "W", "F", "C", "N", "D", "I001"] +lint.ignore = [ + "D105", + "D107", + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-exclude = ["__pycache__", "*.egg_info"] +lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] diff --git a/charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 b/charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 new file mode 100755 index 000000000..c64fe9ee8 --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""ExpressJS Charm entrypoint.""" + +import logging +import typing + +import ops +import paas_charm.expressjs + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_charm.expressjs.Charm): + """ExpressJS Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main({{ class_name }}) diff --git a/charmcraft/templates/init-expressjs-framework-26.04/tox.ini.j2 b/charmcraft/templates/init-expressjs-framework-26.04/tox.ini.j2 new file mode 100644 index 000000000..17bbc92fb --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/tox.ini.j2 @@ -0,0 +1,76 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +all_path = {[vars]src_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + ruff +commands = + ruff format {[vars]all_path} + ruff check --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards, and static checks +runner = uv-venv-lock-runner +dependency_groups = + lint + unit + integration +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + pyright {posargs} + +[testenv:unit] +description = Run unit tests +runner = uv-venv-lock-runner +dependency_groups = + unit +commands = + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +runner = uv-venv-lock-runner +dependency_groups = + integration +pass_env = + # The integration tests don't pack the charm. If CHARM_PATH is set, the tests deploy the + # specified .charm file. Otherwise, the tests look for a .charm file in the project dir. + CHARM_PATH +commands = + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} diff --git a/charmcraft/templates/init-expressjs-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-expressjs-framework/charmcraft.yaml.j2 index 7af066512..d44e815d2 100644 --- a/charmcraft/templates/init-expressjs-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-expressjs-framework/charmcraft.yaml.j2 @@ -1,6 +1,6 @@ # This file configures Charmcraft. # See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. -# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com name: {{ name }} diff --git a/charmcraft/templates/init-expressjs-framework/tox.ini.j2 b/charmcraft/templates/init-expressjs-framework/tox.ini.j2 index bd15d819c..bacc920a4 100644 --- a/charmcraft/templates/init-expressjs-framework/tox.ini.j2 +++ b/charmcraft/templates/init-expressjs-framework/tox.ini.j2 @@ -9,7 +9,7 @@ min_version = 4.0.0 [vars] src_path = {tox_root}/src -;tests_path = {tox_root}/tests +tests_path = {tox_root}/tests all_path = {[vars]src_path} [testenv] diff --git a/charmcraft/templates/init-fastapi-framework-26.04/.gitignore.j2 b/charmcraft/templates/init-fastapi-framework-26.04/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-fastapi-framework-26.04/charmcraft.yaml.j2 b/charmcraft/templates/init-fastapi-framework-26.04/charmcraft.yaml.j2 new file mode 100644 index 000000000..f9ce94d84 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/charmcraft.yaml.j2 @@ -0,0 +1,81 @@ +# This file configures Charmcraft. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com + +name: {{ name }} + +type: charm + +base: ubuntu@26.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the FastAPI application. + +# (Required) +description: | + A comprehensive overview of your FastAPI application. + +extensions: + - fastapi-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# valkey: +# interface: valkey_client +# optional: true +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1 +# tracing: +# interface: tracing +# optional: true +# limit: 1 +# smtp: +# interface: smtp +# optional: false +# limit: 1 +# openfga: +# interface: openfga +# optional: false +# limit: 1 +# http-proxy: +# interface: http_proxy +# optional: true +# limit: 1 + diff --git a/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 new file mode 100644 index 000000000..a3c32be91 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,89 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[project] +name = "{{ name }}" +version = "0.0.1" +requires-python = ">=3.10" + +# Dependencies of the charm code +# You should list the dependencies of the code in src/, including any charm libraries from PyPI. +# We recommend using uv to maintain this list. For example, 'uv add charmlibs-pathops'. +# If your code uses any libraries from Charmhub, don't list those libraries here. Instead, add a +# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries, +# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here. +dependencies = [ + "dpcharmlibs-interfaces==1.0.2", + "jinja2==3.1.6", + "jsonschema==4.26", + "ops==3.7.1", + "paas-charm>=1.0", +] +[dependency-groups] +# PYDEPS from libraries that the charm uses. +charmlibs-pydeps = [ + "cosl==1.9.1", + "pydantic==2.13.3", +] +# Dependencies of linting and static type checks +lint = [ + "ruff", + "codespell", + "pyright", +] +# Dependencies of unit tests +unit = [ + "coverage[toml]", + "ops[testing]", + "pytest", +] +# Dependencies of integration tests +integration = [ + "jubilant>=1.8,<2", + "pytest", + "pytest-jubilant>=2.0.1,<3", + "PyYAML", +] + + +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +lint.select = ["E", "W", "F", "C", "N", "D", "I001"] +lint.ignore = [ + "D105", + "D107", + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-exclude = ["__pycache__", "*.egg_info"] +lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] diff --git a/charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 b/charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 new file mode 100755 index 000000000..59042cc4a --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""FastAPI Charm entrypoint.""" + +import logging +import typing + +import ops +import paas_charm.fastapi + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_charm.fastapi.Charm): + """FastAPI Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main({{ class_name }}) diff --git a/charmcraft/templates/init-fastapi-framework-26.04/tox.ini.j2 b/charmcraft/templates/init-fastapi-framework-26.04/tox.ini.j2 new file mode 100644 index 000000000..17bbc92fb --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/tox.ini.j2 @@ -0,0 +1,76 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +all_path = {[vars]src_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + ruff +commands = + ruff format {[vars]all_path} + ruff check --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards, and static checks +runner = uv-venv-lock-runner +dependency_groups = + lint + unit + integration +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + pyright {posargs} + +[testenv:unit] +description = Run unit tests +runner = uv-venv-lock-runner +dependency_groups = + unit +commands = + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +runner = uv-venv-lock-runner +dependency_groups = + integration +pass_env = + # The integration tests don't pack the charm. If CHARM_PATH is set, the tests deploy the + # specified .charm file. Otherwise, the tests look for a .charm file in the project dir. + CHARM_PATH +commands = + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} diff --git a/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 index f64467e1a..c29ebee1d 100644 --- a/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 @@ -1,6 +1,6 @@ # This file configures Charmcraft. -# See https://juju.is/docs/sdk/charmcraft-config for guidance. -# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com name: {{ name }} diff --git a/charmcraft/templates/init-fastapi-framework/tox.ini.j2 b/charmcraft/templates/init-fastapi-framework/tox.ini.j2 index bd15d819c..bacc920a4 100644 --- a/charmcraft/templates/init-fastapi-framework/tox.ini.j2 +++ b/charmcraft/templates/init-fastapi-framework/tox.ini.j2 @@ -9,7 +9,7 @@ min_version = 4.0.0 [vars] src_path = {tox_root}/src -;tests_path = {tox_root}/tests +tests_path = {tox_root}/tests all_path = {[vars]src_path} [testenv] diff --git a/charmcraft/templates/init-flask-framework-26.04/.gitignore.j2 b/charmcraft/templates/init-flask-framework-26.04/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-flask-framework-26.04/charmcraft.yaml.j2 b/charmcraft/templates/init-flask-framework-26.04/charmcraft.yaml.j2 new file mode 100644 index 000000000..e4b2276d9 --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/charmcraft.yaml.j2 @@ -0,0 +1,80 @@ +# This file configures Charmcraft. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com + +name: {{ name }} + +type: charm + +base: ubuntu@26.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the Flask application. + +# (Required) +description: | + A comprehensive overview of your Flask application. + +extensions: + - flask-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# valkey: +# interface: valkey_client +# optional: true +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1 +# tracing: +# interface: tracing +# optional: true +# limit: 1 +# smtp: +# interface: smtp +# optional: false +# limit: 1 +# openfga: +# interface: openfga +# optional: false +# limit: 1 +# http-proxy: +# interface: http_proxy +# optional: true +# limit: 1 diff --git a/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 new file mode 100644 index 000000000..a3c32be91 --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,89 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[project] +name = "{{ name }}" +version = "0.0.1" +requires-python = ">=3.10" + +# Dependencies of the charm code +# You should list the dependencies of the code in src/, including any charm libraries from PyPI. +# We recommend using uv to maintain this list. For example, 'uv add charmlibs-pathops'. +# If your code uses any libraries from Charmhub, don't list those libraries here. Instead, add a +# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries, +# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here. +dependencies = [ + "dpcharmlibs-interfaces==1.0.2", + "jinja2==3.1.6", + "jsonschema==4.26", + "ops==3.7.1", + "paas-charm>=1.0", +] +[dependency-groups] +# PYDEPS from libraries that the charm uses. +charmlibs-pydeps = [ + "cosl==1.9.1", + "pydantic==2.13.3", +] +# Dependencies of linting and static type checks +lint = [ + "ruff", + "codespell", + "pyright", +] +# Dependencies of unit tests +unit = [ + "coverage[toml]", + "ops[testing]", + "pytest", +] +# Dependencies of integration tests +integration = [ + "jubilant>=1.8,<2", + "pytest", + "pytest-jubilant>=2.0.1,<3", + "PyYAML", +] + + +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +lint.select = ["E", "W", "F", "C", "N", "D", "I001"] +lint.ignore = [ + "D105", + "D107", + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-exclude = ["__pycache__", "*.egg_info"] +lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] diff --git a/charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 b/charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 new file mode 100755 index 000000000..76f7e7d62 --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""Flask Charm entrypoint.""" + +import logging +import typing + +import ops +import paas_charm.flask + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_charm.flask.Charm): + """Flask Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main({{ class_name }}) diff --git a/charmcraft/templates/init-flask-framework-26.04/tox.ini.j2 b/charmcraft/templates/init-flask-framework-26.04/tox.ini.j2 new file mode 100644 index 000000000..17bbc92fb --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/tox.ini.j2 @@ -0,0 +1,76 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +all_path = {[vars]src_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + ruff +commands = + ruff format {[vars]all_path} + ruff check --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards, and static checks +runner = uv-venv-lock-runner +dependency_groups = + lint + unit + integration +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + pyright {posargs} + +[testenv:unit] +description = Run unit tests +runner = uv-venv-lock-runner +dependency_groups = + unit +commands = + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +runner = uv-venv-lock-runner +dependency_groups = + integration +pass_env = + # The integration tests don't pack the charm. If CHARM_PATH is set, the tests deploy the + # specified .charm file. Otherwise, the tests look for a .charm file in the project dir. + CHARM_PATH +commands = + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} diff --git a/charmcraft/templates/init-flask-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-flask-framework/charmcraft.yaml.j2 index 005f12446..ddfde47ce 100644 --- a/charmcraft/templates/init-flask-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-flask-framework/charmcraft.yaml.j2 @@ -1,5 +1,5 @@ # This file configures Charmcraft. -# See https://juju.is/docs/sdk/charmcraft-config for guidance. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. # For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com name: {{ name }} diff --git a/charmcraft/templates/init-flask-framework/tox.ini.j2 b/charmcraft/templates/init-flask-framework/tox.ini.j2 index bd15d819c..bacc920a4 100644 --- a/charmcraft/templates/init-flask-framework/tox.ini.j2 +++ b/charmcraft/templates/init-flask-framework/tox.ini.j2 @@ -9,7 +9,7 @@ min_version = 4.0.0 [vars] src_path = {tox_root}/src -;tests_path = {tox_root}/tests +tests_path = {tox_root}/tests all_path = {[vars]src_path} [testenv] diff --git a/charmcraft/templates/init-go-framework-26.04/.gitignore.j2 b/charmcraft/templates/init-go-framework-26.04/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-go-framework-26.04/charmcraft.yaml.j2 b/charmcraft/templates/init-go-framework-26.04/charmcraft.yaml.j2 new file mode 100644 index 000000000..5686435af --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/charmcraft.yaml.j2 @@ -0,0 +1,80 @@ +# This file configures Charmcraft. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com + +name: {{ name }} + +type: charm + +base: ubuntu@26.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the Go application. + +# (Required) +description: | + A comprehensive overview of your Go application. + +extensions: + - go-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# valkey: +# interface: valkey_client +# optional: true +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1 +# tracing: +# interface: tracing +# optional: true +# limit: 1 +# smtp: +# interface: smtp +# optional: false +# limit: 1 +# openfga: +# interface: openfga +# optional: false +# limit: 1 +# http-proxy: +# interface: http_proxy +# optional: true +# limit: 1 diff --git a/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 new file mode 100644 index 000000000..a3c32be91 --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,89 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[project] +name = "{{ name }}" +version = "0.0.1" +requires-python = ">=3.10" + +# Dependencies of the charm code +# You should list the dependencies of the code in src/, including any charm libraries from PyPI. +# We recommend using uv to maintain this list. For example, 'uv add charmlibs-pathops'. +# If your code uses any libraries from Charmhub, don't list those libraries here. Instead, add a +# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries, +# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here. +dependencies = [ + "dpcharmlibs-interfaces==1.0.2", + "jinja2==3.1.6", + "jsonschema==4.26", + "ops==3.7.1", + "paas-charm>=1.0", +] +[dependency-groups] +# PYDEPS from libraries that the charm uses. +charmlibs-pydeps = [ + "cosl==1.9.1", + "pydantic==2.13.3", +] +# Dependencies of linting and static type checks +lint = [ + "ruff", + "codespell", + "pyright", +] +# Dependencies of unit tests +unit = [ + "coverage[toml]", + "ops[testing]", + "pytest", +] +# Dependencies of integration tests +integration = [ + "jubilant>=1.8,<2", + "pytest", + "pytest-jubilant>=2.0.1,<3", + "PyYAML", +] + + +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +lint.select = ["E", "W", "F", "C", "N", "D", "I001"] +lint.ignore = [ + "D105", + "D107", + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-exclude = ["__pycache__", "*.egg_info"] +lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] diff --git a/charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 b/charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 new file mode 100755 index 000000000..969734515 --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""Go Charm entrypoint.""" + +import logging +import typing + +import ops +import paas_charm.go + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_charm.go.Charm): + """Go Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main({{ class_name }}) diff --git a/charmcraft/templates/init-go-framework-26.04/tox.ini.j2 b/charmcraft/templates/init-go-framework-26.04/tox.ini.j2 new file mode 100644 index 000000000..17bbc92fb --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/tox.ini.j2 @@ -0,0 +1,76 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +all_path = {[vars]src_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + ruff +commands = + ruff format {[vars]all_path} + ruff check --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards, and static checks +runner = uv-venv-lock-runner +dependency_groups = + lint + unit + integration +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + pyright {posargs} + +[testenv:unit] +description = Run unit tests +runner = uv-venv-lock-runner +dependency_groups = + unit +commands = + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +runner = uv-venv-lock-runner +dependency_groups = + integration +pass_env = + # The integration tests don't pack the charm. If CHARM_PATH is set, the tests deploy the + # specified .charm file. Otherwise, the tests look for a .charm file in the project dir. + CHARM_PATH +commands = + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} diff --git a/charmcraft/templates/init-go-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-go-framework/charmcraft.yaml.j2 index bb44201f1..a2ef6f9b7 100644 --- a/charmcraft/templates/init-go-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-go-framework/charmcraft.yaml.j2 @@ -1,6 +1,6 @@ # This file configures Charmcraft. -# See https://juju.is/docs/sdk/charmcraft-config for guidance. -# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com name: {{ name }} diff --git a/charmcraft/templates/init-go-framework/tox.ini.j2 b/charmcraft/templates/init-go-framework/tox.ini.j2 index bd15d819c..bacc920a4 100644 --- a/charmcraft/templates/init-go-framework/tox.ini.j2 +++ b/charmcraft/templates/init-go-framework/tox.ini.j2 @@ -9,7 +9,7 @@ min_version = 4.0.0 [vars] src_path = {tox_root}/src -;tests_path = {tox_root}/tests +tests_path = {tox_root}/tests all_path = {[vars]src_path} [testenv] diff --git a/charmcraft/templates/init-kubernetes/charmcraft.yaml.j2 b/charmcraft/templates/init-kubernetes/charmcraft.yaml.j2 index d487d5213..121833a4d 100644 --- a/charmcraft/templates/init-kubernetes/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-kubernetes/charmcraft.yaml.j2 @@ -69,3 +69,33 @@ resources: # The test_deploy function in tests/integration/test_charm.py reads upstream-source # to determine which OCI image to use when running the charm's integration tests. upstream-source: some-repo/some-image:some-tag + +# Charm libraries from Charmhub that the charm uses. +# Run `charmcraft fetch-libs` after init to download these into lib/. +# After fetching, inspect the PYDEPS comment in each library and add any +# listed packages to the [project].dependencies in pyproject.toml. +# https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/#charm-libs +charm-libs: + - lib: prometheus_k8s.prometheus_scrape + version: "0" + - lib: loki_k8s.loki_push + version: "1" + - lib: grafana_k8s.grafana_dashboard + version: "0" + +# Relations that this charm provides to other applications. +# https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/#provides +provides: + # Expose the workload's Prometheus metrics endpoint for scraping. + metrics-endpoint: + interface: prometheus_scrape + # Register Grafana dashboards for this charm's workload. + grafana-dashboard: + interface: grafana_dashboard + +# Relations that this charm requires from other applications. +# https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/#requires +requires: + # Forward workload logs to a Loki instance (e.g. from COS lite). + logging: + interface: loki_push diff --git a/charmcraft/templates/init-kubernetes/pyproject.toml.j2 b/charmcraft/templates/init-kubernetes/pyproject.toml.j2 index 8bc5a08ae..2e73eef64 100644 --- a/charmcraft/templates/init-kubernetes/pyproject.toml.j2 +++ b/charmcraft/templates/init-kubernetes/pyproject.toml.j2 @@ -46,7 +46,10 @@ show_missing = true [tool.pytest.ini_options] minversion = "6.0" +log_cli = true log_cli_level = "INFO" +log_cli_format = "%(levelname)s %(name)s %(message)s" +filterwarnings = ["error"] # Linting tools configuration [tool.ruff] diff --git a/charmcraft/templates/init-kubernetes/src/charm.py.j2 b/charmcraft/templates/init-kubernetes/src/charm.py.j2 index 5fbc7bd1e..40b897188 100755 --- a/charmcraft/templates/init-kubernetes/src/charm.py.j2 +++ b/charmcraft/templates/init-kubernetes/src/charm.py.j2 @@ -9,6 +9,11 @@ import time import ops +# Observability charm libraries — run `charmcraft fetch-libs` to download these. +from charms.grafana_k8s.v0.grafana_dashboard import GrafanaDashboardProvider +from charms.loki_k8s.v1.loki_push import LogForwarder +from charms.prometheus_k8s.v0.prometheus_scrape import MetricsEndpointProvider + # A standalone module for workload-specific logic (no charming concerns): import {{ workload_module }} @@ -25,6 +30,18 @@ class {{ class_name }}(ops.CharmBase): framework.observe(self.on["some_container"].pebble_ready, self._on_pebble_ready) self.container = self.unit.get_container("some-container") + # Metrics: tell Prometheus where to scrape this workload's metrics. + # Configure your workload container to expose Prometheus metrics on port 8080 + # at the /metrics path (adjust the port to match your workload). + self._metrics_endpoint = MetricsEndpointProvider( + self, + jobs=[{"static_configs": [{"targets": ["*:8080"]}]}], + ) + # Logs: forward workload container logs to Loki via the 'logging' relation. + self._log_forwarder = LogForwarder(self) + # Dashboards: register the Grafana dashboard bundled under src/grafana_dashboards/. + self._grafana_dashboard = GrafanaDashboardProvider(self) + def _on_pebble_ready(self, event: ops.PebbleReadyEvent): """Handle pebble-ready event.""" self.unit.status = ops.MaintenanceStatus("starting workload") diff --git a/charmcraft/templates/init-kubernetes/src/grafana_dashboards/overview.json.j2 b/charmcraft/templates/init-kubernetes/src/grafana_dashboards/overview.json.j2 new file mode 100644 index 000000000..c10a8845c --- /dev/null +++ b/charmcraft/templates/init-kubernetes/src/grafana_dashboards/overview.json.j2 @@ -0,0 +1,72 @@ +{ + "title": "{{ name }} Overview", + "description": "Overview dashboard for the {{ name }} charm. Add panels here to visualise your workload metrics.", + "tags": ["{{ name }}"], + "style": "dark", + "timezone": "browser", + "editable": true, + "graphTooltip": 0, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "refresh": "30s", + "schemaVersion": 27, + "version": 1, + "links": [], + "templating": { + "list": [ + { + "name": "juju_application", + "label": "Application", + "type": "query", + "datasource": "Prometheus", + "query": "label_values(up{juju_charm=\"{{ name }}\"},juju_application)", + "refresh": 2, + "includeAll": false, + "multi": false, + "hide": 0 + } + ] + }, + "panels": [ + { + "id": 1, + "type": "stat", + "title": "Workload Up", + "description": "Whether Prometheus can reach the workload metrics endpoint.", + "targets": [ + { + "expr": "up{juju_application=\"$juju_application\"}" + } + ], + "fieldConfig": { + "defaults": { + "mappings": [ + {"type": "value", "options": {"0": {"text": "Down", "color": "red"}}}, + {"type": "value", "options": {"1": {"text": "Up", "color": "green"}}} + ], + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "red", "value": null}, + {"color": "green", "value": 1} + ] + } + } + }, + "gridPos": {"h": 4, "w": 6, "x": 0, "y": 0} + }, + { + "id": 2, + "type": "text", + "title": "Getting Started", + "options": { + "mode": "markdown", + "content": "## {{ name }}\n\nThis is a starter dashboard. Replace or extend these panels with metrics from your workload.\n\n- Configure your workload to expose Prometheus metrics on port **8080** at **/metrics**.\n- Relate to `prometheus-k8s` and `grafana-k8s` from the [COS lite bundle](https://documentation.ubuntu.com/cos-lite/) to activate collection.\n" + }, + "gridPos": {"h": 4, "w": 18, "x": 6, "y": 0} + } + ] +} diff --git a/charmcraft/templates/init-kubernetes/tests/integration/conftest.py.j2 b/charmcraft/templates/init-kubernetes/tests/integration/conftest.py.j2 index ed5792676..24d35a9bc 100644 --- a/charmcraft/templates/init-kubernetes/tests/integration/conftest.py.j2 +++ b/charmcraft/templates/init-kubernetes/tests/integration/conftest.py.j2 @@ -2,7 +2,7 @@ # See LICENSE file for licensing details. # # The integration tests use the Jubilant library and the pytest-jubilant plugin. -# See https://documentation.ubuntu.com/ops/latest/howto/write-integration-tests-for-a-charm/ +# See https://canonical.com/juju/docs/ops/latest/howto/write-integration-tests-for-a-charm/ import os import pathlib @@ -12,7 +12,7 @@ import pytest @pytest.fixture(scope="session") def charm(): - """Return the path of the charm under test.""" + """Return the absolute path of the charm under test.""" charm = os.environ.get("CHARM_PATH") if not charm: charm_dir = pathlib.Path() # Assume the current working directory is the charm root. diff --git a/charmcraft/templates/init-kubernetes/tests/integration/test_charm.py.j2 b/charmcraft/templates/init-kubernetes/tests/integration/test_charm.py.j2 index 49c5c32a9..53ea8cb95 100644 --- a/charmcraft/templates/init-kubernetes/tests/integration/test_charm.py.j2 +++ b/charmcraft/templates/init-kubernetes/tests/integration/test_charm.py.j2 @@ -2,7 +2,7 @@ # See LICENSE file for licensing details. # # The integration tests use the Jubilant library and the pytest-jubilant plugin. -# See https://documentation.ubuntu.com/ops/latest/howto/write-integration-tests-for-a-charm/ +# See https://canonical.com/juju/docs/ops/latest/howto/write-integration-tests-for-a-charm/ # # pytest-jubilant provides a module-scoped `juju` fixture that creates a temporary Juju model. # The `charm` fixture is defined in conftest.py. @@ -37,3 +37,82 @@ def test_workload_version_is_set(charm: pathlib.Path, juju: jubilant.Juju): """Check that the correct version of the workload is running.""" version = juju.status().apps["{{ name }}"].version assert version == "3.14" # Replace 3.14 by the expected version of the workload. + + +# --------------------------------------------------------------------------- +# Observability integration tests +# +# Full COS integration requires a deployed COS lite stack. +# See https://documentation.ubuntu.com/cos-lite/ for deployment instructions. +# +# To run these tests against a real COS stack, remove the @pytest.mark.skip +# decorators and ensure the COS applications are available in your model. +# --------------------------------------------------------------------------- + + +@pytest.mark.skip( + reason=( + "Requires a COS lite stack. Deploy cos-lite from the Charmhub bundle " + "and remove this skip to run full observability integration tests." + ) +) +def test_metrics_collection(charm: pathlib.Path, juju: jubilant.Juju): + """Verify Prometheus can scrape the workload metrics endpoint.""" + resources = { + "some-container-image": METADATA["resources"]["some-container-image"]["upstream-source"] + } + juju.deploy(charm, app="{{ name }}", resources=resources) + juju.wait(jubilant.all_active) + + # Relate to the prometheus-k8s charm from COS lite. + juju.integrate("{{ name }}:metrics-endpoint", "prometheus-k8s:metrics-endpoint") + juju.wait(jubilant.all_active) + + # Verify the relation is joined and both applications are active. + status = juju.status() + app_status = status.apps["{{ name }}"] + assert app_status.is_active, f"{{ name }} is not active: {app_status.status}" + + +@pytest.mark.skip( + reason=( + "Requires a COS lite stack. Deploy cos-lite from the Charmhub bundle " + "and remove this skip to run full observability integration tests." + ) +) +def test_grafana_dashboard_registered(charm: pathlib.Path, juju: jubilant.Juju): + """Verify that the Grafana dashboard is registered via the grafana-dashboard relation.""" + resources = { + "some-container-image": METADATA["resources"]["some-container-image"]["upstream-source"] + } + juju.deploy(charm, app="{{ name }}", resources=resources) + juju.wait(jubilant.all_active) + + # Relate to the grafana-k8s charm from COS lite. + juju.integrate("{{ name }}:grafana-dashboard", "grafana-k8s:grafana-dashboard") + juju.wait(jubilant.all_active) + + status = juju.status() + assert status.apps["{{ name }}"].is_active + + +@pytest.mark.skip( + reason=( + "Requires a COS lite stack. Deploy cos-lite from the Charmhub bundle " + "and remove this skip to run full observability integration tests." + ) +) +def test_log_forwarding(charm: pathlib.Path, juju: jubilant.Juju): + """Verify workload logs are forwarded to Loki via the logging relation.""" + resources = { + "some-container-image": METADATA["resources"]["some-container-image"]["upstream-source"] + } + juju.deploy(charm, app="{{ name }}", resources=resources) + juju.wait(jubilant.all_active) + + # Relate to the loki-k8s charm from COS lite. + juju.integrate("{{ name }}:logging", "loki-k8s:logging") + juju.wait(jubilant.all_active) + + status = juju.status() + assert status.apps["{{ name }}"].is_active diff --git a/charmcraft/templates/init-kubernetes/tests/unit/test_charm.py.j2 b/charmcraft/templates/init-kubernetes/tests/unit/test_charm.py.j2 index 06af5544d..963d8cc96 100644 --- a/charmcraft/templates/init-kubernetes/tests/unit/test_charm.py.j2 +++ b/charmcraft/templates/init-kubernetes/tests/unit/test_charm.py.j2 @@ -1,7 +1,12 @@ # Copyright {{ year }} {{ author }} # See LICENSE file for licensing details. # -# To learn more about testing, see https://documentation.ubuntu.com/ops/latest/explanation/testing/ +# To learn more about testing, see https://canonical.com/juju/docs/ops/latest/explanation/testing/ +# +# NOTE: charm libraries must be downloaded before running tests: +# charmcraft fetch-libs + +import json import ops import pytest @@ -93,3 +98,66 @@ def test_pebble_ready_service_not_ready(): # Act & assert: with pytest.raises(testing.errors.UncaughtCharmError): ctx.run(ctx.on.pebble_ready(container_in), state_in) + + +# --------------------------------------------------------------------------- +# Observability tests +# These tests verify the COS integration wired in charm.__init__. +# They require the charm libraries to be downloaded first: +# charmcraft fetch-libs +# --------------------------------------------------------------------------- + + +def test_metrics_endpoint_relation_joined(): + """Prometheus scrape jobs are published when metrics-endpoint relation joins.""" + # Arrange: + ctx = testing.Context({{ class_name }}) + rel = testing.Relation("metrics-endpoint") + # The MetricsEndpointProvider only writes app data when the unit is the leader. + state_in = testing.State(relations={rel}, leader=True) + + # Act: fire relation-joined so the provider populates the relation data. + state_out = ctx.run(ctx.on.relation_joined(rel), state_in) + + # Assert: the provider should have written the scrape job spec to app data. + rel_out = state_out.get_relation(rel.id) + assert "scrape_jobs" in rel_out.local_app_data, ( + "MetricsEndpointProvider should set 'scrape_jobs' in app relation data" + ) + scrape_jobs = json.loads(rel_out.local_app_data["scrape_jobs"]) + assert len(scrape_jobs) >= 1 + # The template configures scraping on port 8080 — verify the target is present. + targets = scrape_jobs[0]["static_configs"][0]["targets"] + assert any("8080" in t for t in targets), f"Expected port 8080 in targets, got {targets}" + + +def test_grafana_dashboard_relation_joined(): + """A dashboard is registered when the grafana-dashboard relation joins.""" + # Arrange: + ctx = testing.Context({{ class_name }}) + rel = testing.Relation("grafana-dashboard") + state_in = testing.State(relations={rel}, leader=True) + + # Act: + state_out = ctx.run(ctx.on.relation_joined(rel), state_in) + + # Assert: the provider should have written dashboard data to app data. + rel_out = state_out.get_relation(rel.id) + assert "dashboards" in rel_out.local_app_data, ( + "GrafanaDashboardProvider should set 'dashboards' in app relation data" + ) + # The 'dashboards' value is a JSON-encoded dict of dashboard titles to content. + dashboards = json.loads(rel_out.local_app_data["dashboards"]) + assert len(dashboards) >= 1, "At least one dashboard should be registered" + + +def test_logging_relation_joined(): + """Charm handles the logging relation-joined event without error.""" + # The LogForwarder (requirer side) configures Pebble log forwarding once + # the remote Loki provider sets its endpoint. This test verifies that a + # bare relation-joined on the requirer side does not raise. + ctx = testing.Context({{ class_name }}) + rel = testing.Relation("logging") + state_in = testing.State(relations={rel}) + # Should not raise: + ctx.run(ctx.on.relation_joined(rel), state_in) diff --git a/charmcraft/templates/init-machine/charmcraft.yaml.j2 b/charmcraft/templates/init-machine/charmcraft.yaml.j2 index 640dd8b1f..a4bc3b4d0 100644 --- a/charmcraft/templates/init-machine/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-machine/charmcraft.yaml.j2 @@ -47,3 +47,20 @@ config: Acceptable values are: "info", "debug", "warning", "error" and "critical" default: "info" type: string + +# Charm libraries from Charmhub that the charm uses. +# Run `charmcraft fetch-libs` after init to download these into lib/. +# After fetching, inspect the PYDEPS comment in each library and add any +# listed packages to the [project].dependencies in pyproject.toml. +# https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/#charm-libs +charm-libs: + - lib: cos_agent.cos_agent + version: "0" + +# Relations that this charm provides to other applications. +# https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/#provides +provides: + # Bundle metrics, logs, and dashboards to the grafana-agent machine charm. + # `juju integrate :cos-agent grafana-agent:cos-agent` + cos-agent: + interface: cos_agent diff --git a/charmcraft/templates/init-machine/pyproject.toml.j2 b/charmcraft/templates/init-machine/pyproject.toml.j2 index b8f1f8edf..e116c4dac 100644 --- a/charmcraft/templates/init-machine/pyproject.toml.j2 +++ b/charmcraft/templates/init-machine/pyproject.toml.j2 @@ -45,7 +45,10 @@ show_missing = true [tool.pytest.ini_options] minversion = "6.0" +log_cli = true log_cli_level = "INFO" +log_cli_format = "%(levelname)s %(name)s %(message)s" +filterwarnings = ["error"] # Linting tools configuration [tool.ruff] diff --git a/charmcraft/templates/init-machine/src/charm.py.j2 b/charmcraft/templates/init-machine/src/charm.py.j2 index ec9ec3554..3f33819f2 100644 --- a/charmcraft/templates/init-machine/src/charm.py.j2 +++ b/charmcraft/templates/init-machine/src/charm.py.j2 @@ -8,6 +8,9 @@ import logging import ops +# Observability charm library — run `charmcraft fetch-libs` to download this. +from charms.cos_agent.v0.cos_agent import COSAgentProvider + # A standalone module for workload-specific logic (no charming concerns): import {{ workload_module }} @@ -22,6 +25,19 @@ class {{ class_name }}(ops.CharmBase): framework.observe(self.on.install, self._on_install) framework.observe(self.on.start, self._on_start) + # Observability: bundle metrics, logs, and dashboards for grafana-agent. + # COSAgentProvider passes the configuration to the grafana-agent machine + # charm via the 'cos-agent' relation. grafana-agent then: + # - scrapes the workload metrics endpoint at port 8080 /metrics + # - forwards logs to Loki + # - ships the dashboard JSON to Grafana + # Adjust the metrics port to match your workload's actual port. + self._cos_agent = COSAgentProvider( + self, + metrics_endpoints=[{"path": "/metrics", "port": 8080}], + dashboard_dirs=["./src/grafana_dashboards"], + ) + def _on_install(self, event: ops.InstallEvent): """Install the workload on the machine.""" {{ workload_module }}.install() diff --git a/charmcraft/templates/init-machine/src/grafana_dashboards/overview.json.j2 b/charmcraft/templates/init-machine/src/grafana_dashboards/overview.json.j2 new file mode 100644 index 000000000..7f0b188eb --- /dev/null +++ b/charmcraft/templates/init-machine/src/grafana_dashboards/overview.json.j2 @@ -0,0 +1,72 @@ +{ + "title": "{{ name }} Overview", + "description": "Overview dashboard for the {{ name }} charm. Add panels here to visualise your workload metrics.", + "tags": ["{{ name }}"], + "style": "dark", + "timezone": "browser", + "editable": true, + "graphTooltip": 0, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "refresh": "30s", + "schemaVersion": 27, + "version": 1, + "links": [], + "templating": { + "list": [ + { + "name": "juju_application", + "label": "Application", + "type": "query", + "datasource": "Prometheus", + "query": "label_values(up{juju_charm=\"{{ name }}\"},juju_application)", + "refresh": 2, + "includeAll": false, + "multi": false, + "hide": 0 + } + ] + }, + "panels": [ + { + "id": 1, + "type": "stat", + "title": "Workload Up", + "description": "Whether grafana-agent can reach the workload metrics endpoint.", + "targets": [ + { + "expr": "up{juju_application=\"$juju_application\"}" + } + ], + "fieldConfig": { + "defaults": { + "mappings": [ + {"type": "value", "options": {"0": {"text": "Down", "color": "red"}}}, + {"type": "value", "options": {"1": {"text": "Up", "color": "green"}}} + ], + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "red", "value": null}, + {"color": "green", "value": 1} + ] + } + } + }, + "gridPos": {"h": 4, "w": 6, "x": 0, "y": 0} + }, + { + "id": 2, + "type": "text", + "title": "Getting Started", + "options": { + "mode": "markdown", + "content": "## {{ name }}\n\nThis is a starter dashboard. Replace or extend these panels with metrics from your workload.\n\n- Configure your workload to expose Prometheus metrics on port **8080** at **/metrics**.\n- Deploy `grafana-agent` (machine) and relate via `cos-agent` to activate collection.\n- See [COS lite docs](https://documentation.ubuntu.com/cos-lite/) for the full observability stack.\n" + }, + "gridPos": {"h": 4, "w": 18, "x": 6, "y": 0} + } + ] +} diff --git a/charmcraft/templates/init-machine/tests/integration/conftest.py.j2 b/charmcraft/templates/init-machine/tests/integration/conftest.py.j2 index ed5792676..24d35a9bc 100644 --- a/charmcraft/templates/init-machine/tests/integration/conftest.py.j2 +++ b/charmcraft/templates/init-machine/tests/integration/conftest.py.j2 @@ -2,7 +2,7 @@ # See LICENSE file for licensing details. # # The integration tests use the Jubilant library and the pytest-jubilant plugin. -# See https://documentation.ubuntu.com/ops/latest/howto/write-integration-tests-for-a-charm/ +# See https://canonical.com/juju/docs/ops/latest/howto/write-integration-tests-for-a-charm/ import os import pathlib @@ -12,7 +12,7 @@ import pytest @pytest.fixture(scope="session") def charm(): - """Return the path of the charm under test.""" + """Return the absolute path of the charm under test.""" charm = os.environ.get("CHARM_PATH") if not charm: charm_dir = pathlib.Path() # Assume the current working directory is the charm root. diff --git a/charmcraft/templates/init-machine/tests/integration/test_charm.py.j2 b/charmcraft/templates/init-machine/tests/integration/test_charm.py.j2 index b632102ad..765d819f9 100644 --- a/charmcraft/templates/init-machine/tests/integration/test_charm.py.j2 +++ b/charmcraft/templates/init-machine/tests/integration/test_charm.py.j2 @@ -2,7 +2,7 @@ # See LICENSE file for licensing details. # # The integration tests use the Jubilant library and the pytest-jubilant plugin. -# See https://documentation.ubuntu.com/ops/latest/howto/write-integration-tests-for-a-charm/ +# See https://canonical.com/juju/docs/ops/latest/howto/write-integration-tests-for-a-charm/ # # pytest-jubilant provides a module-scoped `juju` fixture that creates a temporary Juju model. # The `charm` fixture is defined in conftest.py. @@ -31,3 +31,60 @@ def test_workload_version_is_set(charm: pathlib.Path, juju: jubilant.Juju): """Check that the correct version of the workload is running.""" version = juju.status().apps["{{ name }}"].version assert version == "3.14" # Replace 3.14 by the expected version of the workload. + + +# --------------------------------------------------------------------------- +# Observability integration tests +# +# Full COS integration on machine charms requires: +# - A deployed COS lite stack (prometheus, loki, grafana) +# - The `grafana-agent` machine charm deployed in the same model +# +# To run these tests: +# 1. Deploy COS lite: https://documentation.ubuntu.com/cos-lite/ +# 2. Deploy grafana-agent: juju deploy grafana-agent +# 3. Remove the @pytest.mark.skip decorator from the tests below. +# --------------------------------------------------------------------------- + + +@pytest.mark.skip( + reason=( + "Requires grafana-agent (machine) and a COS lite stack. " + "See comment above for setup instructions." + ) +) +def test_cos_agent_relation(charm: pathlib.Path, juju: jubilant.Juju): + """Verify that the charm integrates with grafana-agent via the cos-agent relation.""" + juju.deploy(charm, app="{{ name }}") + juju.deploy("grafana-agent", channel="latest/stable") + juju.wait(jubilant.all_active) + + # The cos-agent relation bundles metrics, logs, and dashboards to grafana-agent. + juju.integrate("{{ name }}:cos-agent", "grafana-agent:cos-agent") + juju.wait(jubilant.all_active) + + status = juju.status() + assert status.apps["{{ name }}"].is_active, ( + f"{{ name }} should be active after integrating with grafana-agent, " + f"got: {status.apps['{{ name }}'].status}" + ) + assert status.apps["grafana-agent"].is_active + + +@pytest.mark.skip( + reason=( + "Requires grafana-agent (machine) and a COS lite stack. " + "See comment above for setup instructions." + ) +) +def test_metrics_are_scraped(charm: pathlib.Path, juju: jubilant.Juju): + """Verify that Prometheus receives metrics from the workload via grafana-agent.""" + juju.deploy(charm, app="{{ name }}") + juju.deploy("grafana-agent", channel="latest/stable") + juju.integrate("{{ name }}:cos-agent", "grafana-agent:cos-agent") + # Cross-model: relate grafana-agent to Prometheus in the COS model. + # juju integrate grafana-agent:send-remote-write prometheus-remote-write:receive-remote-write + juju.wait(jubilant.all_active) + + status = juju.status() + assert status.apps["{{ name }}"].is_active diff --git a/charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 b/charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 index 38faa6a5d..db211a57d 100644 --- a/charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 +++ b/charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 @@ -1,7 +1,12 @@ # Copyright {{ year }} {{ author }} # See LICENSE file for licensing details. # -# To learn more about testing, see https://documentation.ubuntu.com/ops/latest/explanation/testing/ +# To learn more about testing, see https://canonical.com/juju/docs/ops/latest/explanation/testing/ +# +# NOTE: charm libraries must be downloaded before running tests: +# charmcraft fetch-libs + +import json import pytest from ops import testing @@ -24,3 +29,49 @@ def test_start(monkeypatch: pytest.MonkeyPatch): # Assert: assert state_out.workload_version is not None assert state_out.unit_status == testing.ActiveStatus() + + +# --------------------------------------------------------------------------- +# Observability tests +# These tests verify the COS integration wired in charm.__init__. +# They require the charm libraries to be downloaded first: +# charmcraft fetch-libs +# --------------------------------------------------------------------------- + + +def test_cos_agent_relation_joined(): + """COS agent configuration is published when grafana-agent joins the cos-agent relation.""" + # Arrange: + ctx = testing.Context({{ class_name }}) + rel = testing.Relation("cos-agent") + state_in = testing.State(relations={rel}) + + # Act: fire relation-joined so COSAgentProvider populates the relation data. + state_out = ctx.run(ctx.on.relation_joined(rel), state_in) + + # Assert: the provider should have written its configuration to unit relation data. + # COSAgentProvider serialises metrics endpoints, dashboard dirs, etc. into + # the 'config' key of the local unit's relation data bucket. + rel_out = state_out.get_relation(rel.id) + assert "config" in rel_out.local_unit_data, ( + "COSAgentProvider should set 'config' in unit relation data" + ) + config = json.loads(rel_out.local_unit_data["config"]) + # Verify the metrics endpoint the template configures is present. + endpoints = config.get("metrics_scrape_jobs", []) + assert len(endpoints) >= 1, "At least one metrics scrape job should be configured" + + +def test_cos_agent_relation_joined_dashboard_registered(): + """Dashboard directory is included in COS agent config when the relation joins.""" + ctx = testing.Context({{ class_name }}) + rel = testing.Relation("cos-agent") + state_in = testing.State(relations={rel}) + + state_out = ctx.run(ctx.on.relation_joined(rel), state_in) + + rel_out = state_out.get_relation(rel.id) + config = json.loads(rel_out.local_unit_data.get("config", "{}")) + # The dashboards list comes from the JSON files in src/grafana_dashboards/. + dashboards = config.get("dashboards", []) + assert len(dashboards) >= 1, "At least one dashboard should be registered via COS agent" diff --git a/charmcraft/templates/init-spring-boot-framework-26.04/.gitignore.j2 b/charmcraft/templates/init-spring-boot-framework-26.04/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-spring-boot-framework-26.04/charmcraft.yaml.j2 b/charmcraft/templates/init-spring-boot-framework-26.04/charmcraft.yaml.j2 new file mode 100644 index 000000000..0ea2f0fd9 --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/charmcraft.yaml.j2 @@ -0,0 +1,81 @@ +# This file configures Charmcraft. +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com + +name: {{ name }} + +type: charm + +base: ubuntu@26.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the Spring Boot application. + +# (Required) +description: | + A comprehensive overview of your Spring Boot application. + +extensions: + - spring-boot-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# valkey: +# interface: valkey_client +# optional: true +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1 +# tracing: +# interface: tracing +# optional: true +# limit: 1 +# smtp: +# interface: smtp +# optional: false +# limit: 1 +# openfga: +# interface: openfga +# optional: false +# limit: 1 +# http-proxy: +# interface: http_proxy +# optional: true +# limit: 1 + diff --git a/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 new file mode 100644 index 000000000..96b231689 --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,89 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[project] +name = "{{ name }}" +version = "0.0.1" +requires-python = ">=3.10" + +# Dependencies of the charm code +# You should list the dependencies of the code in src/, including any charm libraries from PyPI. +# We recommend using uv to maintain this list. For example, 'uv add charmlibs-pathops'. +# If your code uses any libraries from Charmhub, don't list those libraries here. Instead, add a +# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries, +# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here. +dependencies = [ + "dpcharmlibs-interfaces==1.0.2", + "jinja2==3.1.6", + "jsonschema==4.26", + "ops==3.7.1", + "paas-charm>=1.0", +] +[dependency-groups] +# PYDEPS from libraries that the charm uses. +charmlibs-pydeps = [ + "cosl==1.9.1", + "pydantic==2.13.3", +] +# Dependencies of linting and static type checks +lint = [ + "ruff", + "codespell", + "pyright", +] +# Dependencies of unit tests +unit = [ + "coverage[toml]", + "ops[testing]", + "pytest", +] +# Dependencies of integration tests +integration = [ + "jubilant>=1.8,<2", + "pytest", + "pytest-jubilant>=2.0.1,<3", + "PyYAML", +] + + +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +lint.select = ["E", "W", "F", "C", "N", "D", "I001"] +lint.extend-ignore = [ + "D105", + "D107", + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-exclude = ["__pycache__", "*.egg_info"] +lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] diff --git a/charmcraft/templates/init-spring-boot-framework-26.04/src/charm.py.j2 b/charmcraft/templates/init-spring-boot-framework-26.04/src/charm.py.j2 new file mode 100755 index 000000000..a90bbe2de --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/src/charm.py.j2 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""Spring Boot Charm entrypoint.""" + +import logging +import typing + +import ops +import paas_charm.springboot + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_charm.springboot.Charm): + """Spring Boot Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main({{ class_name }}) diff --git a/charmcraft/templates/init-spring-boot-framework-26.04/tox.ini.j2 b/charmcraft/templates/init-spring-boot-framework-26.04/tox.ini.j2 new file mode 100644 index 000000000..17bbc92fb --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/tox.ini.j2 @@ -0,0 +1,76 @@ +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +all_path = {[vars]src_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + ruff +commands = + ruff format {[vars]all_path} + ruff check --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards, and static checks +runner = uv-venv-lock-runner +dependency_groups = + lint + unit + integration +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + pyright {posargs} + +[testenv:unit] +description = Run unit tests +runner = uv-venv-lock-runner +dependency_groups = + unit +commands = + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +runner = uv-venv-lock-runner +dependency_groups = + integration +pass_env = + # The integration tests don't pack the charm. If CHARM_PATH is set, the tests deploy the + # specified .charm file. Otherwise, the tests look for a .charm file in the project dir. + CHARM_PATH +commands = + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} diff --git a/charmcraft/templates/init-spring-boot-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-spring-boot-framework/charmcraft.yaml.j2 index e955623d5..b2c06f8dc 100644 --- a/charmcraft/templates/init-spring-boot-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-spring-boot-framework/charmcraft.yaml.j2 @@ -1,6 +1,6 @@ # This file configures Charmcraft. -# See https://juju.is/docs/sdk/charmcraft-config for guidance. -# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com +# See https://documentation.ubuntu.com/charmcraft/stable/reference/files/charmcraft-yaml-file/ for guidance. +# For questions or help, visit https://matrix.to/#/#12-factor-charms:ubuntu.com name: {{ name }} diff --git a/charmcraft/templates/init-spring-boot-framework/tox.ini.j2 b/charmcraft/templates/init-spring-boot-framework/tox.ini.j2 index bd15d819c..bacc920a4 100644 --- a/charmcraft/templates/init-spring-boot-framework/tox.ini.j2 +++ b/charmcraft/templates/init-spring-boot-framework/tox.ini.j2 @@ -9,7 +9,7 @@ min_version = 4.0.0 [vars] src_path = {tox_root}/src -;tests_path = {tox_root}/tests +tests_path = {tox_root}/tests all_path = {[vars]src_path} [testenv] diff --git a/charmcraft/utils/store.py b/charmcraft/utils/store.py index bbe286a1f..58a89ac97 100644 --- a/charmcraft/utils/store.py +++ b/charmcraft/utils/store.py @@ -17,20 +17,16 @@ from collections.abc import Iterable -from craft_store import endpoints - def get_packages( charms: Iterable[str] = (), bundles: Iterable[str] = () -) -> list[endpoints.Package]: - """Get a list of packages from charms and bundles.""" +) -> list[dict[str, str]]: + """Get a list of package specs from charms and bundles. + + The store's token request API expects each package as a dict with + "type" and "name" keys. + """ return [ - *( - endpoints.Package(package_type="charm", package_name=charm) - for charm in charms - ), - *( - endpoints.Package(package_type="bundle", package_name=bundle) - for bundle in bundles - ), + *({"type": "charm", "name": charm} for charm in charms), + *({"type": "bundle", "name": bundle} for bundle in bundles), ] diff --git a/docs/_static/js/overwrite-links.js b/docs/_static/js/overwrite-links.js new file mode 100644 index 000000000..425e0eaff --- /dev/null +++ b/docs/_static/js/overwrite-links.js @@ -0,0 +1,38 @@ +// Replace oldDomain with newDomain +const oldDomain = 'canonical-charmcraft-staging.readthedocs-hosted.com'; +const newDomain = 'canonical.com/juju/docs/charmcraft'; + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function overwriteMatchingAnchorUrls(container) { + if (!container) return; + + const anchors = container.querySelectorAll('a[href], link[href]'); + const oldDomainRegex = new RegExp(escapeRegExp(oldDomain), 'g'); + + anchors.forEach(anchor => { + anchor.href = anchor.href.replace(oldDomainRegex, newDomain); + }); +} + +overwriteMatchingAnchorUrls(document.querySelector('header')); + +// Use a MutationObserver to wait for the RTD flyout element to appear in the DOM +const observer = new MutationObserver(function(mutations, obs) { + + const rtdFlyout = document.querySelector('readthedocs-flyout'); + if (!rtdFlyout) return; + + obs.disconnect(); + + rtdFlyout.addEventListener('click', function() { + const shadowRoot = rtdFlyout.shadowRoot; + if (!shadowRoot) return; + + overwriteMatchingAnchorUrls(shadowRoot); + }); +}); + +observer.observe(document.body, { childList: true, subtree: true }); diff --git a/docs/conf.py b/docs/conf.py index f3ebd48a7..87e19df2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,6 +3,7 @@ import os import pathlib import sys +import re import craft_parts_docs import craft_application_docs @@ -30,18 +31,17 @@ # Version string in sidebar if os.environ.get("READTHEDOCS_VERSION_TYPE", "external") == "external": # PR or local build - # Because of Autotools, we can safely assume the version starts with `n.n` + # Because of setuptools, we can safely assume the version starts with `n.n` major, minor, *_ = charmcraft.__version__.split(".") release = f"{major}.{minor}" else: # Branch build - rtd_version = os.environ.get("READTHEDOCS_VERSION", "latest") - release = "dev" if rtd_version == "latest" else rtd_version + release = os.environ.get("READTHEDOCS_VERSION", "latest") # Copyright string; shown at the bottom of the page copyright = "2023-%s, %s" % (datetime.date.today().year, author) # Documentation website URL -ogp_site_url = "https://documentation.ubuntu.com/charmcraft/" +ogp_site_url = "https://canonical.com/juju/docs/charmcraft" # Preview name of the documentation website ogp_site_name = project @@ -87,7 +87,7 @@ # } # Project slug; see https://meta.discourse.org/t/what-is-category-slug/87897 -# slug = '' +slug = "juju/docs/charmcraft" ######################### @@ -95,19 +95,19 @@ ######################### # Use RTD canonical URL to ensure duplicate pages have a specific canonical URL -html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/") +html_baseurl = f"{ogp_site_url}/{release}/" # sphinx-sitemap uses html_baseurl to generate the full URL for each page: -sitemap_url_scheme = '{link}' +sitemap_url_scheme = "{link}" # Include `lastmod` dates in the sitemap: # sitemap_show_lastmod = True # Exclude generated pages from the sitemap: sitemap_excludes = [ - '404/', - 'genindex/', - 'search/', + "404/", + "genindex/", + "search/", ] @@ -251,12 +251,13 @@ # Adds custom CSS files, located under 'html_static_path' html_css_files = [ - 'css/cookie-banner.css' + "css/cookie-banner.css" ] # Adds custom JavaScript files, located under 'html_static_path' html_js_files = [ - 'js/bundle.js', + "js/bundle.js", + "js/overwrite-links.js", ] # Specifies a reST snippet to be appended to each .rst file @@ -292,13 +293,13 @@ # Add configuration for intersphinx mapping intersphinx_mapping = { "python": ("https://docs.python.org/3", None), - "craft-parts": ("https://canonical-craft-parts.readthedocs-hosted.com/latest/", None), - "juju": ("https://documentation.ubuntu.com/juju/3.6/", None), - "ops": ("https://documentation.ubuntu.com/ops/latest/", None), - "rockcraft": ("https://documentation.ubuntu.com/rockcraft/stable/", None), - "12-factor": ("https://canonical-12-factor-app-support.readthedocs-hosted.com/latest/", None), - "charmlibs": ("https://documentation.ubuntu.com/charmlibs/", None), - "multipass": ("https://documentation.ubuntu.com/multipass/latest/", None), + "craft-parts": ("https://documentation.ubuntu.com/craft-parts/latest", None), + "juju": ("https://canonical.com/juju/docs/juju-cli/3.6", None), + "ops": ("https://canonical.com/juju/docs/ops/latest", None), + "rockcraft": ("https://documentation.ubuntu.com/rockcraft/stable", None), + "12-factor": ("https://canonical.com/juju/docs/12-factor/latest", None), + "charmlibs": ("https://canonical.com/juju/docs/charmlibs", None), + "multipass": ("https://documentation.ubuntu.com/multipass/latest", None), } @@ -343,3 +344,21 @@ def link_common_docs(library_name: str) -> None: link_common_docs("craft-parts") link_common_docs("craft-application") + +# Source 12-factor framework versions from Spread test materials and inject into docs +def sub_12f_version(prolog: str, path: str, package: str, variable: str) -> str: + path = os.path.abspath(os.path.join(os.path.dirname(__file__), path)) + + with open(path, encoding="utf-8") as f: + match = re.search(rf"{re.escape(package)}([\d.]+)", f.read()) + if match: + version = match.group(1) + prolog += f"\n.. |{variable}| replace:: {version}" + + return prolog + +rst_prolog = sub_12f_version(rst_prolog, "tutorial/code/django/requirements.txt", "Django==", "conf_django_version") + +rst_prolog = sub_12f_version(rst_prolog, "tutorial/code/fastapi/requirements.txt", "fastapi[standard]==", "conf_fastapi_version") + +rst_prolog = sub_12f_version(rst_prolog, "tutorial/code/flask/requirements.txt", "Flask==", "conf_flask_version") diff --git a/docs/howto/build-remotely.rst b/docs/howto/build-remotely.rst new file mode 100644 index 000000000..9483346ff --- /dev/null +++ b/docs/howto/build-remotely.rst @@ -0,0 +1,5 @@ +.. |star| replace:: charm +.. |app-command| replace:: charmcraft +.. |Starcraft| replace:: Charmcraft + +.. include:: ../common/craft-application/how-to-guides/build-remotely.rst diff --git a/docs/howto/code/custom-action-clearsession/task.yaml b/docs/howto/code/custom-action-clearsession/task.yaml index 58ea339d2..39701e7b4 100644 --- a/docs/howto/code/custom-action-clearsession/task.yaml +++ b/docs/howto/code/custom-action-clearsession/task.yaml @@ -80,7 +80,7 @@ execute: | # Deploy Django app juju deploy \ - ./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + ./django-hello-world_$(dpkg --print-architecture).charm \ django-hello-world --resource \ django-app-image=localhost:32000/django-hello-world:0.1 @@ -122,7 +122,7 @@ execute: | # refresh the deployment juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --path=./django-hello-world_$(dpkg --print-architecture).charm \ --resource django-app-image=localhost:32000/django-hello-world:0.1 # wait for the Django app to come back to active idle diff --git a/docs/howto/code/custom-action-updatelogfile/task.yaml b/docs/howto/code/custom-action-updatelogfile/task.yaml index 1316cd577..32bcdbd71 100644 --- a/docs/howto/code/custom-action-updatelogfile/task.yaml +++ b/docs/howto/code/custom-action-updatelogfile/task.yaml @@ -86,7 +86,7 @@ execute: | # [docs:deploy-flask-app] juju deploy \ - ./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + ./flask-hello-world_$(dpkg --print-architecture).charm \ flask-hello-world --resource \ flask-app-image=localhost:32000/flask-hello-world:0.1 # [docs:deploy-flask-app-end] @@ -115,16 +115,22 @@ execute: | # add custom action to charmcraft.yaml cat $HOME/flask-app/updatelogfile_action_charmcraft.yaml >> charmcraft.yaml + # add requests to charm's requirements.txt + echo "requests" >> $HOME/flask-app/charm/requirements.txt + # add custom action to src/charm.py cat $HOME/flask-app/updatelogfile_action_charm.py > $HOME/flask-app/charm/src/charm.py + # executable permissions on src/charm.py + chmod +x $HOME/flask-app/charm/src/charm.py + # charmcraft clean and charmcraft pack charmcraft clean charmcraft pack # refresh the deployment juju refresh flask-hello-world \ - --path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --path=./flask-hello-world_$(dpkg --print-architecture).charm \ --resource flask-app-image=localhost:32000/flask-hello-world:0.1 # wait for the Flask app to come back to active idle diff --git a/docs/howto/code/flask-async/task.yaml b/docs/howto/code/flask-async/task.yaml index adfc8b093..5c248bfd6 100644 --- a/docs/howto/code/flask-async/task.yaml +++ b/docs/howto/code/flask-async/task.yaml @@ -93,7 +93,7 @@ execute: | juju set-model-constraints -m flask-async-app arch=$(dpkg --print-architecture) # [docs:deploy-juju-model] - juju deploy ./flask-async-app_ubuntu-22.04-$(dpkg --print-architecture).charm \ + juju deploy ./flask-async-app_$(dpkg --print-architecture).charm \ flask-async-app --resource \ flask-app-image=localhost:32000/flask-async-app:0.1 # [docs:deploy-juju-model-end] diff --git a/docs/howto/index.rst b/docs/howto/index.rst index d0d13d567..798fe2a7e 100644 --- a/docs/howto/index.rst +++ b/docs/howto/index.rst @@ -36,6 +36,7 @@ Publish and release When you're ready to distribute your charm, Charmcraft provides commands to register it, publish it to Charmhub, and manage its releases. +- :ref:`how-to-build-remotely` - :ref:`manage-the-current-charmhub-user` - :ref:`manage-names` - :ref:`manage-icons` @@ -74,6 +75,7 @@ update a charm to the new base: manage-extensions manage-resources manage-libraries + build-remotely shared-cache pack-a-hooks-based-charm-with-charmcraft pack-a-reactive-charm-with-charmcraft diff --git a/docs/howto/manage-charmcraft.rst b/docs/howto/manage-charmcraft.rst index 9c18f858d..8cb1de6df 100644 --- a/docs/howto/manage-charmcraft.rst +++ b/docs/howto/manage-charmcraft.rst @@ -108,7 +108,7 @@ In an isolated environment Another way to install Charmcraft is via `Multipass`_. This is a good way to install it on any platform, as it will give you an isolated development environment. -First, `install Multipass `_. +First, `install Multipass `_. Second, use Multipass to provision a virtual machine. The following command will launch a fresh new VM with 4 cores, 8GB RAM and a 20GB disk and the name ‘charm-dev': diff --git a/docs/howto/manage-charms.rst b/docs/howto/manage-charms.rst index be210e747..0c79442a6 100644 --- a/docs/howto/manage-charms.rst +++ b/docs/howto/manage-charms.rst @@ -14,33 +14,38 @@ Manage charms Initialise a charm ------------------ -.. admonition:: Best practice - :class: hint +Before you initialise a charm project, decide what to call your charm. - If you're setting up a ``git`` repository: name it using the pattern - ``-operator``. For the charm name, see :ref:`specify-a-name`. +A charm's name is typically based on the name of the charm's workload. Use a different +naming convention if your charm doesn't operate a workload. For detailed guidance, see +:external+ops:ref:`Ops | How to initialise your project `. The Ops guidance +also explains how to name your charm's Git repository. -To initialise a charm project, create a directory for your charm, enter it, then run -``charmcraft init`` with the ``--profile`` flag followed by a suitable profile name (for -machine charms: ``machine``; for Kubernetes charms: ``kubernetes`` or -``flask-framework``); that will create all the necessary files and even prepopulate them -with useful content. +To initialise a charm project, enter the directory that will contain your charm (likely +in your charm's repository), then run ``charmcraft init``: .. code-block:: bash - charmcraft init --profile + charmcraft init --name --profile -.. dropdown:: Example session +This will create all the necessary files and populate them with useful content. - .. code-block:: bash +If the charm name you want is different from the current directory name, don't skip the +``--name`` argument. Otherwise the charm name will match the directory name. - mkdir my-flask-app-k8s - cd my-flask-app-k8s/ - charmcraft init --profile flask-framework +```` can be ``kubernetes`` for a Kubernetes charm, ``machine`` for a machine +charm, or a 12-factor app profile such as ``flask-framework``. If you don't specify a +profile, you get the ``kubernetes`` profile. + +.. dropdown:: Example session .. terminal:: + :dir: ~/my-flask-app-k8s-operator + + charmcraft init --name my-flask-app-k8s --profile flask-framework + + Charmed operator package file and directory tree initialised. - Charmed operator package file and directory tree initialised Now edit the following package files to provide fundamental charm metadata and other information: @@ -48,24 +53,18 @@ with useful content. src/charm.py README.md - .. code-block:: bash + .. terminal:: + :dir: ~/my-flask-app-k8s-operator ls -R - .. terminal:: - .: - charmcraft.yaml requirements.txt src + charmcraft.yaml pyproject.toml src tox.ini ./src: charm.py -The command also allows you to not specify any profile (in that case you get the -``kubernetes`` profile -- a minimal profile with scaffolding for a Kubernetes charm) -and has flags that you can use to specify a different directory to operate -in, a charm name different from the name of the root directory, etc. - - See more: :ref:`ref_commands_revisions`, :ref:`profile`, :ref:`files` + See more: :ref:`ref_commands_init`, :ref:`profile`, :ref:`files` See more: :ref:`manage-extensions` @@ -498,9 +497,22 @@ Manage secrets See first: :external+juju:ref:`Juju | Manage secrets `, :external+juju:ref:`Juju | Secret ` -To make your charm capable of accepting a user secret, in your charm's -project file, specify the ``config`` key with the ``type`` subkey set to -``secret``. +Charms can interact with Juju secrets in three ways: + +- **Charm owns a secret**: the charm creates and manages the secret, such as a + database credential shared with a related app via relation data. +- **Charm observes a charm-owned secret**: the charm reads a secret created by + another charm, with the secret ID passed via relation data. +- **Charm observes a user secret**: the charm reads a secret created by a Juju + user (``juju add-secret``), with the secret URI passed via a configuration + option of ``type: secret``. + + See more: :external+ops:ref:`Ops | Manage secrets `, + :external+juju:ref:`Juju | Secret ` + +The third case — **user secrets** — is the one that requires a Charmcraft +declaration. To allow a Juju user to provide a secret to your charm, declare a +configuration option of ``type: secret`` in your charm's project file: See more: :ref:`charmcraft-yaml-key-config` diff --git a/docs/howto/manage-extensions.rst b/docs/howto/manage-extensions.rst index 8ab98965c..6bcdfa939 100644 --- a/docs/howto/manage-extensions.rst +++ b/docs/howto/manage-extensions.rst @@ -1,3 +1,6 @@ +.. meta:: + :description: How to view and manage extensions. + .. _manage-extensions: Manage extensions @@ -17,9 +20,15 @@ For example: .. code-block:: bash $ charmcraft list-extensions - Extension name Supported bases Experimental bases - ---------------- ----------------- -------------------- - flask-framework ubuntu@22.04 + Extension name Supported bases Experimental bases + --------------------- -------------------------- -------------------- + django-framework ubuntu@22.04, ubuntu@24.04 ubuntu@26.04 + expressjs-framework ubuntu@24.04 ubuntu@26.04 + fastapi-framework ubuntu@24.04 ubuntu@26.04 + flask-framework ubuntu@22.04, ubuntu@24.04 ubuntu@26.04 + go-framework ubuntu@24.04 ubuntu@26.04 + spring-boot-framework ubuntu@24.04 ubuntu@26.04 + View details about the extension in use --------------------------------------- diff --git a/docs/howto/manage-web-app-charms/configure-web-app-charm.rst b/docs/howto/manage-web-app-charms/configure-web-app-charm.rst index e02a9869c..2d3094ca6 100644 --- a/docs/howto/manage-web-app-charms/configure-web-app-charm.rst +++ b/docs/howto/manage-web-app-charms/configure-web-app-charm.rst @@ -223,7 +223,8 @@ Add the custom action to the project file: :language: yaml :caption: charmcraft.yaml -Add ``import requests`` to the start of ``src/charm.py``, then define +Add ``requests`` to the ``charm/requirements.txt`` file, and +add ``import requests`` to the start of ``src/charm.py``. Finally, define your custom action as part of the class and provide the function definition: .. literalinclude:: ../code/custom-action-updatelogfile/updatelogfile_action_charm.py diff --git a/docs/howto/manage-web-app-charms/integrate-web-app-charm.rst b/docs/howto/manage-web-app-charms/integrate-web-app-charm.rst index 83828a55e..458473223 100644 --- a/docs/howto/manage-web-app-charms/integrate-web-app-charm.rst +++ b/docs/howto/manage-web-app-charms/integrate-web-app-charm.rst @@ -101,13 +101,27 @@ You must prepare an ingress if you wish to integrate your 12-factor web app with the `Canonical Observability Stack (COS) `_. COS relies on the Traefik ingress to expose, for example, Grafana. -On MicroK8s, Traefik requires the MetalLB load balancer to be enabled which -requires an IP range. Provide the IP range and enable the addon with: +Traefik requires a load balancer to be enabled with an IP range. Provide the +IP range and enable the load balancer with: -.. code-block:: bash +.. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc') + microk8s enable metallb:$IPADDR-$IPADDR + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash - IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc') - microk8s enable metallb:$IPADDR-$IPADDR + IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc') + sudo k8s set load-balancer.l2-mode=true load-balancer.cidrs=$IPADDR-$IPADDR + sudo k8s enable load-balancer Deploy and integrate observability to the 12-factor app with: diff --git a/docs/howto/manage-web-app-charms/use-web-app-charm.rst b/docs/howto/manage-web-app-charms/use-web-app-charm.rst index 5dbf5550f..89dd0897e 100644 --- a/docs/howto/manage-web-app-charms/use-web-app-charm.rst +++ b/docs/howto/manage-web-app-charms/use-web-app-charm.rst @@ -1,8 +1,23 @@ +.. meta:: + :description: How to use a 12-factor app charm, including troubleshooting, observability, and TLS. + .. _use-12-factor-charms: Use a 12-factor app charm ========================= +.. important:: + + Extension behaviour differs between Ubuntu base versions. + + - Ubuntu 22.04 LTS (Jammy) and Ubuntu 24.04 LTS (Noble) use the original + ``paas-charm`` templates. + - Ubuntu 26.04 LTS (Resolute) and higher use the ``-26.04`` templates, with + updated configuration and behaviour. + + If you are targeting Ubuntu 26.04 LTS or higher, use the ``-26.04`` templates. + The guides on this page apply to both base groups unless noted otherwise. + .. _use-12-factor-charms-admin-user-django: Create an admin user for a Django app charm @@ -61,48 +76,101 @@ To view the Pebble logs for a deployed web app, run: .. tab-set:: - .. tab-item:: Django - :sync: django + .. tab-item:: Ubuntu 22.04 and 24.04 + :sync: base-22-24 - .. code-block:: bash + .. tab-set:: - juju ssh --container django-app /0 pebble logs + .. tab-item:: Django + :sync: django - .. tab-item:: Express - :sync: express + .. code-block:: bash - .. code-block:: bash + juju ssh --container django-app /0 pebble logs - juju ssh --container app /0 pebble logs + .. tab-item:: Express + :sync: express - .. tab-item:: FastAPI - :sync: fastapi + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 pebble logs - juju ssh --container app /0 pebble logs + .. tab-item:: FastAPI + :sync: fastapi - .. tab-item:: Flask - :sync: flask + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 pebble logs - juju ssh --container flask-app /0 pebble logs + .. tab-item:: Flask + :sync: flask - .. tab-item:: Go - :sync: go + .. code-block:: bash - .. code-block:: bash + juju ssh --container flask-app /0 pebble logs - juju ssh --container app /0 pebble logs + .. tab-item:: Go + :sync: go - .. tab-item:: Spring Boot - :sync: spring-boot + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 pebble logs + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble logs + + .. tab-item:: Ubuntu 26.04 and higher + :sync: base-26-plus + + .. tab-set:: + + .. tab-item:: Django + :sync: django + + .. code-block:: bash + + juju ssh --container app /0 pebble logs + + .. tab-item:: Express + :sync: express - juju ssh /0 \ - PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble logs + .. code-block:: bash + + juju ssh --container app /0 pebble logs + + .. tab-item:: FastAPI + :sync: fastapi + + .. code-block:: bash + + juju ssh --container app /0 pebble logs + + .. tab-item:: Flask + :sync: flask + + .. code-block:: bash + + juju ssh --container app /0 pebble logs + + .. tab-item:: Go + :sync: go + + .. code-block:: bash + + juju ssh --container app /0 pebble logs + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble logs .. seealso:: @@ -116,48 +184,101 @@ To view more details about the web app itself, run: .. tab-set:: - .. tab-item:: Django - :sync: django + .. tab-item:: Ubuntu 22.04 and 24.04 + :sync: base-22-24 - .. code-block:: bash + .. tab-set:: - juju ssh --container django-app /0 pebble plan + .. tab-item:: Django + :sync: django - .. tab-item:: Express - :sync: express + .. code-block:: bash - .. code-block:: bash + juju ssh --container django-app /0 pebble plan - juju ssh --container app /0 pebble plan + .. tab-item:: Express + :sync: express - .. tab-item:: FastAPI - :sync: fastapi + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 pebble plan - juju ssh --container app /0 pebble plan + .. tab-item:: FastAPI + :sync: fastapi - .. tab-item:: Flask - :sync: flask + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 pebble plan - juju ssh --container flask-app /0 pebble plan + .. tab-item:: Flask + :sync: flask - .. tab-item:: Go - :sync: go + .. code-block:: bash - .. code-block:: bash + juju ssh --container flask-app /0 pebble plan - juju ssh --container app /0 pebble plan + .. tab-item:: Go + :sync: go - .. tab-item:: Spring Boot - :sync: spring-boot + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 pebble plan + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash - juju ssh /0 \ - PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble plan + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble plan + + .. tab-item:: Ubuntu 26.04 and higher + :sync: base-26-plus + + .. tab-set:: + + .. tab-item:: Django + :sync: django + + .. code-block:: bash + + juju ssh --container app /0 pebble plan + + .. tab-item:: Express + :sync: express + + .. code-block:: bash + + juju ssh --container app /0 pebble plan + + .. tab-item:: FastAPI + :sync: fastapi + + .. code-block:: bash + + juju ssh --container app /0 pebble plan + + .. tab-item:: Flask + :sync: flask + + .. code-block:: bash + + juju ssh --container app /0 pebble plan + + .. tab-item:: Go + :sync: go + + .. code-block:: bash + + juju ssh --container app /0 pebble plan + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble plan This command provides information on what services you may start in your app and what environment variables exist (i.e., what is available for the app to @@ -176,54 +297,114 @@ Juju container: .. tab-set:: - .. tab-item:: Django - :sync: django + .. tab-item:: Ubuntu 22.04 and 24.04 + :sync: base-22-24 - .. code-block:: bash + .. tab-set:: - juju ssh --container django-app /0 \ - pebble exec --context=django -- bash + .. tab-item:: Django + :sync: django - .. tab-item:: Express - :sync: express + .. code-block:: bash - .. code-block:: bash + juju ssh --container django-app /0 \ + pebble exec --context=django -- bash - juju ssh --container app /0 \ - pebble exec --context=expressjs -- bash + .. tab-item:: Express + :sync: express - .. tab-item:: FastAPI - :sync: fastapi + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 \ + pebble exec --context=expressjs -- bash - juju ssh --container app /0 \ - pebble exec --context=fastapi -- bash + .. tab-item:: FastAPI + :sync: fastapi - .. tab-item:: Flask - :sync: flask + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 \ + pebble exec --context=fastapi -- bash - juju ssh --container flask-app /0 \ - pebble exec --context=flask -- bash + .. tab-item:: Flask + :sync: flask - .. tab-item:: Go - :sync: go + .. code-block:: bash - .. code-block:: bash + juju ssh --container flask-app /0 \ + pebble exec --context=flask -- bash - juju ssh --container app /0 \ - pebble exec --context=go -- bash + .. tab-item:: Go + :sync: go - .. tab-item:: Spring Boot - :sync: spring-boot + .. code-block:: bash - .. code-block:: bash + juju ssh --container app /0 \ + pebble exec --context=go -- bash + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket \ + /charm/bin/pebble exec --context=spring-boot -- bash + + + .. tab-item:: Ubuntu 26.04 and higher + :sync: base-26-plus + + .. tab-set:: + + .. tab-item:: Django + :sync: django + + .. code-block:: bash + + juju ssh --container app /0 \ + pebble exec --context=django -- bash + + .. tab-item:: Express + :sync: express + + .. code-block:: bash + + juju ssh --container app /0 \ + pebble exec --context=expressjs -- bash + + .. tab-item:: FastAPI + :sync: fastapi + + .. code-block:: bash + + juju ssh --container app /0 \ + pebble exec --context=fastapi -- bash - juju ssh /0 \ - PEBBLE_SOCKET=/charm/containers/app/pebble.socket \ - /charm/bin/pebble exec --context=spring-boot -- bash + .. tab-item:: Flask + :sync: flask + + .. code-block:: bash + + juju ssh --container app /0 \ + pebble exec --context=flask -- bash + + .. tab-item:: Go + :sync: go + + .. code-block:: bash + + juju ssh --container app /0 \ + pebble exec --context=go -- bash + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket \ + /charm/bin/pebble exec --context=spring-boot -- bash .. important:: @@ -244,24 +425,48 @@ of the container, for instance, ``/django/app``. `_ -Check MicroK8s pod services and logs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Check Kubernetes pod services and logs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Check the currently deployed Kubernetes resources in the ````, which is the same as the Juju model name: -.. code:: +.. tab-set:: - microk8s.kubectl get all -n + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash -This command outputs a list of all the MicroK8s resources in the web app's + microk8s.kubectl get all -n + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl get all -n + +This command outputs a list of all the Kubernetes resources in the web app's Juju model. -Check the logs for a specific MicroK8s pod: +Check the logs for a specific Kubernetes pod: + +.. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash -.. code:: + microk8s kubectl logs -n - microk8s kubectl logs -n + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n This command outputs the logs of the sidecar container pod. To fetch logs specific to the workload of the web app, you need to specify the container @@ -269,51 +474,177 @@ name of the web app with the ``-c`` option. .. tab-set:: - .. tab-item:: Django - :sync: django + .. tab-item:: Ubuntu 22.04 and 24.04 + :sync: base-22-24 - .. code-block:: bash + .. tab-set:: - microk8s kubectl logs -n -c django-app + .. tab-item:: Django + :sync: django - .. tab-item:: Express - :sync: express + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c django-app - microk8s kubectl logs -n -c app + .. tab-item:: Express + :sync: express - .. tab-item:: FastAPI - :sync: fastapi + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c app - microk8s kubectl logs -n -c app + .. tab-item:: FastAPI + :sync: fastapi - .. tab-item:: Flask - :sync: flask + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c app - microk8s kubectl logs -n -c flask-app + .. tab-item:: Flask + :sync: flask - .. tab-item:: Go - :sync: go + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c flask-app - microk8s kubectl logs -n -c app + .. tab-item:: Go + :sync: go - .. tab-item:: Spring Boot - :sync: spring-boot + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c app + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Ubuntu 26.04 and higher + :sync: base-26-plus + + .. tab-set:: + + .. tab-item:: Django + :sync: django + + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n -c app + + .. tab-item:: Express + :sync: express + + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n -c app + + .. tab-item:: FastAPI + :sync: fastapi + + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n -c app + + .. tab-item:: Flask + :sync: flask + + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n -c app + + .. tab-item:: Go + :sync: go + + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n -c app + + .. tab-item:: Spring Boot + :sync: spring-boot + + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash - microk8s kubectl logs -n -c app + sudo k8s kubectl logs -n -c app .. seealso:: - `MicroK8s | Troubleshooting `_ + - `MicroK8s | Troubleshooting `_ + - `Canonical Kubernetes | Troubleshoot + `_ Check Juju logs ~~~~~~~~~~~~~~~ diff --git a/docs/howto/migrate-plugins/charm-to-python.rst b/docs/howto/migrate-plugins/charm-to-python.rst index 20b0eb2c2..2b7219222 100644 --- a/docs/howto/migrate-plugins/charm-to-python.rst +++ b/docs/howto/migrate-plugins/charm-to-python.rst @@ -67,7 +67,7 @@ complete set of packages needed in the charm's virtual environment. A few examples include: - `uv export `_ - - `pip-compile `_ + - `pip-compile `_ - `pip freeze `_ A basic ``requirements.txt`` file for a charm with no dependencies other than the diff --git a/docs/howto/migrate-plugins/charm-to-uv.rst b/docs/howto/migrate-plugins/charm-to-uv.rst index 8700a41c8..02ddf1b06 100644 --- a/docs/howto/migrate-plugins/charm-to-uv.rst +++ b/docs/howto/migrate-plugins/charm-to-uv.rst @@ -168,7 +168,7 @@ that uses the :ref:`craft_parts_dump_plugin`: .. code-block:: yaml :caption: charmcraft.yaml - :emphasize-lines: 9-13 + :emphasize-lines: 9-14 parts: my-charm: diff --git a/docs/reference/files/charmcraft-yaml-file.rst b/docs/reference/files/charmcraft-yaml-file.rst index 5ac8c556b..0f3109845 100644 --- a/docs/reference/files/charmcraft-yaml-file.rst +++ b/docs/reference/files/charmcraft-yaml-file.rst @@ -635,29 +635,16 @@ determines the name administrators will ultimately use to deploy the charm. E.g. name: +**Value:** ``[][-k8s]``, using only ASCII lowercase letters, +numbers, and hyphens. For example, ``argo-server-k8s``. Use a different pattern if the +charm doesn't operate a workload. See :external+ops:ref:`Ops | Decide your charm's name `. + **Example:** .. literalinclude:: charmcraft-sample-charm.yaml :start-at: name: :end-before: parts: -.. admonition:: Best practice - :class: hint - - The charm name should be slug-oriented (ASCII lowercase letters, numbers, and - hyphens) and follow the pattern ``[][-k8s]``. - For example, ``argo-server-k8s``. - - Include the ``-k8s`` suffix on all charms that run on a Kubernetes cloud, - unless the charm has no workload or you know that there will never be - a machine version of the charm. - - Don't include an organization or publisher in the name. - - Don't add an ``operator`` or ``charm`` prefix or suffix. For naming a - repository, see :ref:`initialise-a-charm`. - - .. _charmcraft-yaml-key-parts: ``parts`` diff --git a/docs/reference/files/lxd-profile-yaml-file.rst b/docs/reference/files/lxd-profile-yaml-file.rst index e95330c87..d49da5cb0 100644 --- a/docs/reference/files/lxd-profile-yaml-file.rst +++ b/docs/reference/files/lxd-profile-yaml-file.rst @@ -10,8 +10,8 @@ of your machine charm project. This file is optional, though it may be useful for charms intended for a machine cloud of the LXD type. - See more: `Juju \| The LXD cloud and - Juju `_ + See more: `Juju \| The LXD cloud and Juju + `__ The file allows you to specify a LXD profile to be applied to the LXD container that your charm is deployed into. The structure of the file diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 3d1cdeb64..1adf0e4d6 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -86,4 +86,5 @@ frameworks. parts/index platforms profile + remote-builds changelog diff --git a/docs/reference/remote-builds.rst b/docs/reference/remote-builds.rst new file mode 100644 index 000000000..9f4831e37 --- /dev/null +++ b/docs/reference/remote-builds.rst @@ -0,0 +1,5 @@ +.. |star| replace:: charm +.. |app-command| replace:: charmcraft +.. |Starcraft| replace:: Charmcraft + +.. include:: ../common/craft-application/reference/remote-builds.rst diff --git a/docs/release-notes/charmcraft-4.2.rst b/docs/release-notes/charmcraft-4.2.rst index 77129c1fd..1ee84e2c4 100644 --- a/docs/release-notes/charmcraft-4.2.rst +++ b/docs/release-notes/charmcraft-4.2.rst @@ -120,6 +120,13 @@ The following issues have been resolved in Charmcraft 4.2.1: using a separate ``metadata.yaml`` file and the ``--project-dir`` argument - `#2620 `__ Multi-base shorthand notation fails on v4.2.0 +- `#2705 `__ refer to inline charmcraft.yaml metadata in pack help + metadata instead of the legacy ``metadata.yaml`` split-file format. + +The following issues have been resolved in Charmcraft 4.2.2: + +- `#2598 `__ Remote build fails + when getting project name Contributors ------------ diff --git a/docs/release-notes/charmcraft-4.3.rst b/docs/release-notes/charmcraft-4.3.rst new file mode 100644 index 000000000..4a33dda96 --- /dev/null +++ b/docs/release-notes/charmcraft-4.3.rst @@ -0,0 +1,181 @@ +.. meta:: + :description: Learn about the new features, changes, and fixes introduced in Charmcraft 4.3. + +.. _release-4.3: + +Charmcraft 4.3 release notes +============================= + +23 June 2026 + +Learn about the new features, changes, and fixes introduced in Charmcraft 4.3. + + +Requirements and compatibility +------------------------------ + +For development and testing, Charmcraft requires a host with a minimum of 4GB RAM +running a Linux distribution compatible with systemd. + +All versions of Charmcraft require the following software: + +- systemd +- `snapd`_ +- Either `LXD`_ or `Multipass`_ + +We recommend you install the `Charmcraft snap `__. It +comes bundled with all its dependencies. + +Non-snap installations of Charmcraft have the following dependencies: + +- Python 3.10 or higher +- libgit2 1.7 +- `skopeo`_ +- `Spread`_ + +What's new +---------- + +Charmcraft 4.3 brings the following new features. + +Ubuntu 26.04 LTS support for the reactive plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :ref:`reactive plugin ` can now build +charms on the Ubuntu 26.04 LTS base. Because the python3-venv package was moved +to the ``universe`` APT repository on Ubuntu 26.04 LTS, the plugin now adds this +repository automatically. + +See :ref:`howto-change-to-ubuntu-26-04` for guidance on migrating your +charm to Ubuntu 26.04 LTS. + +Part naming constraints +~~~~~~~~~~~~~~~~~~~~~~~ + +Part names are now validated more +strictly and cannot contain forward slashes (``/``) in charms built on the ``ubuntu@26.04`` +base or higher. + +Valkey support in 12-factor app init templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The init templates for 12-factor app frameworks (Django, Flask, FastAPI, Express.js, +Go, and Spring Boot) now include a commented-out ``valkey_client`` relation. If you +declare this optional interface in your charm's ``requires``, Charmcraft automatically +injects the necessary dpcharmlibs-interfaces charm Python package. + +If the ``valkey_client`` relation is declared, you can make use of the following +environment variables in your app: + +- ``VALKEY_HOSTNAME`` +- ``VALKEY_PORT`` +- ``VALKEY_USERNAME`` +- ``VALKEY_PASSWORD`` + +See :ref:`extensions` for details. + +Improved test scaffolding +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``charmcraft init`` command now generates charm project files with improved +test scaffolding: + +- Templates now use `pytest-jubilant + `__ as the test runner for + integration tests, replacing the previous custom helpers. The + ``@pytest.mark.juju_setup`` marker is included in integration test scaffolding for + machine and Kubernetes profiles. +- Integration test templates for machine and Kubernetes profiles have improved comments + pointing to the canonical guide on writing integration tests. + +Treat pytest warnings as errors by default +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In charms created with the ``machine`` and ``kubernetes`` profiles, tests will now fail +on any Python warning by default. This behavior was added to bring attention to +deprecated API calls and garbage collection bugs before they reach production. + +To ignore a warning across the project, add an ``ignore`` entry to your +``pyproject.toml`` file, such as: + +.. code-block:: toml + :caption: pyproject.toml + + [tool.pytest.ini_options] + filterwarnings = [ + "error", + "ignore::UserWarning", + ] + +COS directory structure validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When packing a 12-factor app charm, Charmcraft now validates the structure of any +custom COS (Canonical Observability Stack) directory. If the directory layout is +incorrect, Charmcraft reports an error when packing instead of letting an invalid +structure cause errors at runtime. + + +Documentation improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Charmcraft 4.3 includes the following new and updated documentation: + +- :doc:`How to migrate your charm to Ubuntu 26.04 ` +- Documentation for the ``django-secret-key-id`` configuration option +- Valkey integration reference, including environment variables and integration metadata +- Plugin differences noted in the guide on managing charm icons + + +Fixed bugs and issues +--------------------- + +The following issues have been resolved in Charmcraft 4.3.0: + +- `#2600 `__ + ``charmcraft pack -p`` and ``-o`` arguments did not work correctly together + +The following issues have been resolved in Charmcraft 4.3.1: + +- The Poetry plugin was installing the ``python3-poetry-plugin-export`` package + even when a ``poetry-deps`` part was included. + + +Known issues +------------ + +The following issues were reported and are scheduled to be fixed in upcoming +patch releases. + +See individual issue links for any mitigations. + +- `#2078 `__ + ``charmcraft clean`` does not clean all platforms for a charm +- `#1990 `__ Cannot stage + packages with Charmcraft +- `#2492 `__ Internal error when + uploading a charm outside of a project directory + + +Contributors +------------ + +We would like to express a big thank you to all the people who contributed to +this release: + +:literalref:`@activus-d `, +:literalref:`@bepri `, +:literalref:`@canon-cat `, +:literalref:`@danielvnguyen `, +:literalref:`@dimaqq `, +:literalref:`@dwilding `, +:literalref:`@f-atwi `, +:literalref:`@jahn-junior `, +:literalref:`@james-garner-canonical `, +:literalref:`@javierdelapuente `, +:literalref:`@lengau `, +:literalref:`@medubelko `, +:literalref:`@sabaini `, +:literalref:`@steinbro `, +:literalref:`@Thanhphan1147 `, +and :literalref:`@tonyandrewmeyer ` diff --git a/docs/release-notes/charmcraft-4.4.rst b/docs/release-notes/charmcraft-4.4.rst new file mode 100644 index 000000000..08d872c1e --- /dev/null +++ b/docs/release-notes/charmcraft-4.4.rst @@ -0,0 +1,120 @@ +.. meta:: + :description: Learn about the new features, changes, and fixes introduced in Charmcraft 4.4. + +.. _release-4.4: + +Charmcraft 4.4 release notes +============================ + +28 July 2026 + +Learn about the new features, changes, and fixes introduced in Charmcraft 4.4. + + +Requirements and compatibility +------------------------------ + +For development and testing, Charmcraft requires a host with a minimum of 4GB RAM +running a Linux distribution compatible with systemd. + +All versions of Charmcraft require the following software: + +- systemd +- `snapd`_ +- Either `LXD`_ or `Multipass`_ + +We recommend you install the `Charmcraft snap `__. It +comes bundled with all its dependencies. + +Non-snap installations of Charmcraft have the following dependencies: + +- Python 3.10 or higher +- libgit2 1.7 +- `skopeo`_ +- `Spread`_ + +What's new +---------- + +Charmcraft 4.4 brings the following new features. + +Ubuntu One authentication +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Charmcraft now uses Ubuntu One authentication instead of Candid for store +access and publishing workflows. Login is now handled entirely in the CLI, +without a browser-based authentication step. + +12-factor extension and profile updates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Charmcraft 4.4 introduces the following improvements to the 12-factor app +templates and extensions: + +- Extension dispatch support for 12-factor extensions. +- Base support for the Flask and Django extensions. +- Generated tests now treat warnings as errors by default. +- Validation for ``paas-config.yaml`` inputs. + +Documentation improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Charmcraft 4.4 includes the following documentation changes: + +- The guidance on setting up and testing machine and Kubernetes charms was + improved in :ref:`manage-charms` and :ref:`profile`. +- Charm naming guidelines have been made clearer in :ref:`manage-names`. +- :ref:`manage-charms` now clarifies how charms interact with Juju secrets. + + +Fixed bugs and issues +--------------------- + +The following issues have been resolved in Charmcraft 4.4.0: + +- The Poetry plugin was installing the ``python3-poetry-plugin-export`` package + even when a ``poetry-deps`` part was included. + + +Known issues +------------ + +The following issues were reported and are scheduled to be fixed in upcoming +patch releases. + +See individual issue links for any mitigations. + +- `#2078 `__ + ``charmcraft clean`` does not clean all platforms for a charm +- `#1990 `__ Cannot stage + packages with Charmcraft +- `#2492 `__ Internal error when + uploading a charm outside of a project directory +- `#2788 `__ + uv plugin fails on core26: dispatch script uses system Python instead of venv + + +Contributors +------------ + +We would like to express a big thank you to all the people who contributed to +this release: + +:literalref:`@PraaneshSelvaraj `, +:literalref:`@alithethird `, +:literalref:`@bepri `, +:literalref:`@canon-cat `, +:literalref:`@cmatsuoka `, +:literalref:`@dwilding `, +:literalref:`@erinecon `, +:literalref:`@florentianayuwono `, +:literalref:`@jahn-junior `, +:literalref:`@javierdelapuente `, +:literalref:`@lengau `, +:literalref:`@medubelko `, +:literalref:`@mr-cal `, +:literalref:`@smethnani `, +:literalref:`@tigarmo `, +:literalref:`@tonyandrewmeyer `, +:literalref:`@tromai `, +and :literalref:`@upils ` diff --git a/docs/release-notes/charmcraft-4.5.rst b/docs/release-notes/charmcraft-4.5.rst new file mode 100644 index 000000000..246d8448c --- /dev/null +++ b/docs/release-notes/charmcraft-4.5.rst @@ -0,0 +1,124 @@ +:orphan: + +.. meta:: + :description: Learn about the new features, changes, and fixes introduced in Charmcraft 4.5. + +.. _release-4.5: + +Charmcraft 4.5 release notes +============================ + +TBD + +Learn about the new features, changes, and fixes introduced in Charmcraft 4.5. + + +Requirements and compatibility +------------------------------ + +For development and testing, Charmcraft requires a host with a minimum of 4GB RAM +running a Linux distribution compatible with systemd. + +All versions of Charmcraft require the following software: + +- systemd +- `snapd`_ +- Either `LXD`_ or `Multipass`_ + +We recommend you install the `Charmcraft snap `__. It +comes bundled with all its dependencies. + +Non-snap installations of Charmcraft have the following dependencies: + +- Python 3.10 or higher +- libgit2 1.7 +- `skopeo`_ +- `Spread`_ + + +What's new +---------- + +Charmcraft 4.5 brings the following features, integrations, and improvements. + + + +~~~~~~~~~~~~~~~~~~ + + + + +Minor features +-------------- + +Charmcraft 4.5 brings the following minor changes. + + + +~~~~~~~~~~~ + +- + + +Backwards-incompatible changes +------------------------------ + +The following changes are incompatible with previous versions of Charmcraft. + + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + + +Feature deprecations +-------------------- + +The following features are deprecated in Charmcraft 4.5. + + + +~~~~~~~~~~~~~~~~~~~~~~ + + + + +Scheduled feature deprecations +------------------------------ + +The following features will be deprecated in Charmcraft . + + + +~~~~~~~~~~~ + + + + +Fixed bugs and issues +--------------------- + +The following issues have been resolved in Charmcraft 4.5. + +- No entries yet. + + +Known issues +------------ + +The following issues were reported and are scheduled to be fixed in upcoming +patch releases. + +See individual issue links for any mitigations. + +- No entries yet. + + +Contributors +------------ + +We would like to express a big thank you to all the people who contributed to +this release. + +Contributor list is pending. diff --git a/docs/release-notes/index.rst b/docs/release-notes/index.rst index f78115cfe..c2be23597 100644 --- a/docs/release-notes/index.rst +++ b/docs/release-notes/index.rst @@ -14,6 +14,8 @@ Current releases Charmcraft 4 ~~~~~~~~~~~~ +- :doc:`Charmcraft 4.4 ` +- :doc:`Charmcraft 4.3 ` - :doc:`Charmcraft 4.2 ` - :doc:`Charmcraft 4.1 ` - :doc:`Charmcraft 4.0 ` @@ -88,6 +90,8 @@ keeps pace with the OS's new releases and support lifecycle. :hidden: :maxdepth: 1 + Charmcraft 4.4 + Charmcraft 4.3 Charmcraft 4.2 Charmcraft 4.1 Charmcraft 4.0 diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst index c2b5f7a73..ce386b5ac 100644 --- a/docs/reuse/tutorial/setup_edge.rst +++ b/docs/reuse/tutorial/setup_edge.rst @@ -54,12 +54,12 @@ Initialize LXD: lxd init --auto MicroK8s is required to deploy the |12FactorApp| application on Kubernetes. -Let's install MicroK8s using the ``1.31-strict/stable`` track, add the current +Let's install MicroK8s using the ``1.34-strict/stable`` channel, add the current user to the group, and activate the changes: .. code-block:: text - sudo snap install microk8s --channel 1.31-strict/stable + sudo snap install microk8s --channel 1.34-strict/stable sudo adduser $USER snap_microk8s newgrp snap_microk8s @@ -86,7 +86,7 @@ If successful, the terminal will output ``microk8s is running`` along with a list of enabled and disabled add-ons. Juju is required to deploy the |12FactorApp| application. -We'll install Juju using the ``3.6/stable`` track. Since the snap is +We'll install Juju using the ``3.6/stable`` channel. Since the snap is sandboxed, we'll also manually create a directory to contain its files. Once Juju is ready, we initialize it by bootstrapping a development controller: diff --git a/docs/reuse/tutorial/setup_stable.rst b/docs/reuse/tutorial/setup_stable.rst index 6bb1b1481..6bcc778d1 100644 --- a/docs/reuse/tutorial/setup_stable.rst +++ b/docs/reuse/tutorial/setup_stable.rst @@ -49,12 +49,12 @@ Initialize LXD: lxd init --auto MicroK8s is required to deploy the |12FactorApp| application on Kubernetes. -Let's install MicroK8s using the ``1.31-strict/stable`` track, add the current +Let's install MicroK8s using the ``1.34-strict/stable`` channel, add the current user to the group, and activate the changes: .. code-block:: text - sudo snap install microk8s --channel 1.31-strict/stable + sudo snap install microk8s --channel 1.34-strict/stable sudo adduser $USER snap_microk8s newgrp snap_microk8s @@ -82,7 +82,7 @@ If successful, the terminal will output ``microk8s is running`` along with a list of enabled and disabled add-ons. Juju is required to deploy the |12FactorApp| application. -We'll install Juju using the ``3.6/stable`` track. Since the snap is +We'll install Juju using the ``3.6/stable`` channel. Since the snap is sandboxed, we'll also manually create a directory to contain its files. Once Juju is ready, we initialize it by bootstrapping a development controller: diff --git a/docs/tutorial/code/django/requirements.txt b/docs/tutorial/code/django/requirements.txt index 56da67f67..47401236c 100644 --- a/docs/tutorial/code/django/requirements.txt +++ b/docs/tutorial/code/django/requirements.txt @@ -1,3 +1,3 @@ # Make sure to update the version in the tutorial if you update the version here. -Django==5.2.15 +Django==6.0.6 psycopg2-binary==2.9.12 diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 00aa0dab1..0857d61f0 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -5,7 +5,7 @@ # markers for including said instructions # as snippets in the docs. ########################################### -summary: Getting started with Django tutorial +summary: 12-factor Django tutorial kill-timeout: 50m @@ -112,7 +112,7 @@ execute: | # [docs:deploy-django-app] juju deploy \ - ./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + ./django-hello-world_$(dpkg --print-architecture).charm \ django-hello-world --resource \ django-app-image=localhost:32000/django-hello-world:0.1 # [docs:deploy-django-app-end] @@ -184,7 +184,7 @@ execute: | # [docs:refresh-deployment] cd charm juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --path=./django-hello-world_$(dpkg --print-architecture).charm \ --resource django-app-image=localhost:32000/django-hello-world:0.2 # [docs:refresh-deployment-end] @@ -219,7 +219,7 @@ execute: | # [docs:repack-refresh-2nd-deployment] charmcraft pack juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --path=./django-hello-world_$(dpkg --print-architecture).charm \ --resource django-app-image=localhost:32000/django-hello-world:0.3 # [docs:repack-refresh-2nd-deployment-end] diff --git a/docs/tutorial/code/fastapi/requirements.txt b/docs/tutorial/code/fastapi/requirements.txt index dc22bedff..4af336e8c 100644 --- a/docs/tutorial/code/fastapi/requirements.txt +++ b/docs/tutorial/code/fastapi/requirements.txt @@ -1,3 +1,3 @@ # Make sure to update the version in the tutorial if you update the version here. -fastapi[standard]==0.136.1 +fastapi[standard]==0.139.0 psycopg2-binary==2.9.12 diff --git a/docs/tutorial/code/fastapi/task.yaml b/docs/tutorial/code/fastapi/task.yaml index de8117772..69280d78e 100644 --- a/docs/tutorial/code/fastapi/task.yaml +++ b/docs/tutorial/code/fastapi/task.yaml @@ -201,7 +201,7 @@ execute: | curl http://fastapi-hello-world/visitors --resolve fastapi-hello-world:80:127.0.0.1 | grep 2 # [docs:clean-environment] - CHARMCRAFTCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft clean + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft clean # Back out to main directory for cleanup cd .. ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft clean diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 696959ee3..25cc4dba8 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -88,7 +88,7 @@ execute: | # [docs:deploy-flask-app] juju deploy \ - ./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + ./flask-hello-world_$(dpkg --print-architecture).charm \ flask-hello-world --resource \ flask-app-image=localhost:32000/flask-hello-world:0.1 # [docs:deploy-flask-app-end] @@ -131,7 +131,7 @@ execute: | # [docs:refresh-deployment] charmcraft pack juju refresh flask-hello-world \ - --path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --path=./flask-hello-world_$(dpkg --print-architecture).charm \ --resource flask-app-image=localhost:32000/flask-hello-world:0.2 # [docs:refresh-deployment-end] @@ -169,7 +169,7 @@ execute: | # [docs:refresh-2nd-deployment] charmcraft pack juju refresh flask-hello-world \ - --path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --path=./flask-hello-world_$(dpkg --print-architecture).charm \ --resource flask-app-image=localhost:32000/flask-hello-world:0.3 # [docs:refresh-2nd-deployment-end] diff --git a/docs/tutorial/kubernetes-charm-django.rst b/docs/tutorial/kubernetes-charm-django.rst index 2c64d4ec7..f0aae12c9 100644 --- a/docs/tutorial/kubernetes-charm-django.rst +++ b/docs/tutorial/kubernetes-charm-django.rst @@ -46,7 +46,7 @@ than the sufficient resources, the tutorial will take longer to complete. What you'll do ~~~~~~~~~~~~~~ -#. Create a Django app with Django 5.1.4. +#. Create a Django app with Django |conf_django_version|. #. Use that to create a rock with Rockcraft. #. Use that to create a charm with Charmcraft. #. Use that to test, deploy, configure, etc., your Django app on a local @@ -258,7 +258,7 @@ imports to include ``json``, ``os`` and ``secrets``. The top of the ``settings.py`` file should look similar to the following snippet: .. code-block:: python - :caption: ~/django-tutorial/django_hello_world/settings.py + :caption: ~/django-tutorial/django_hello_world/django_hello_world/settings.py :emphasize-lines: 15,16,17 """ @@ -284,7 +284,7 @@ Near the top of the ``settings.py`` file, change the ``SECRET_KEY``, ``DEBUG`` and ``ALLOWED_HOSTS`` variables to: .. code-block:: python - :caption: ~/django-tutorial/django_hello_world/settings.py + :caption: ~/django-tutorial/django_hello_world/django_hello_world/settings.py :emphasize-lines: 2,5,7 # SECURITY WARNING: keep the secret key used in production secret! @@ -300,7 +300,7 @@ We will also use PostgreSQL as the database for our Django app. In ``DATABASES`` variable to: .. code-block:: python - :caption: ~/django-tutorial/django_hello_world/settings.py + :caption: ~/django-tutorial/django_hello_world/django_hello_world/settings.py :emphasize-lines: 3-8 DATABASES = { @@ -608,7 +608,7 @@ Open the ``django_hello_world/urls.py`` file and edit the imports for ``django.urls`` and the value of ``urlpatterns`` like in the following example: .. code-block:: python - :caption: ~/django-tutorial/django_hello_world/urls.py + :caption: ~/django-tutorial/django_hello_world/django_hello_world/urls.py :emphasize-lines: 2,5 from django.contrib import admin diff --git a/docs/tutorial/kubernetes-charm-fastapi.rst b/docs/tutorial/kubernetes-charm-fastapi.rst index 667926f2a..37402a3b5 100644 --- a/docs/tutorial/kubernetes-charm-fastapi.rst +++ b/docs/tutorial/kubernetes-charm-fastapi.rst @@ -46,7 +46,7 @@ than the sufficient resources, the tutorial will take longer to complete. What you'll do ~~~~~~~~~~~~~~ -#. Create a FastAPI app with FastAPI 0.136.1. +#. Create a FastAPI app with FastAPI |conf_fastapi_version|. #. Use that to create a rock with Rockcraft. #. Use that to create a charm with Charmcraft. #. Use that to test, deploy, configure, etc., your FastAPI app on a local diff --git a/docs/tutorial/kubernetes-charm-flask.rst b/docs/tutorial/kubernetes-charm-flask.rst index d6498c79e..9bcf92862 100644 --- a/docs/tutorial/kubernetes-charm-flask.rst +++ b/docs/tutorial/kubernetes-charm-flask.rst @@ -42,7 +42,7 @@ than the sufficient resources, the tutorial will take longer to complete. What you'll do -------------- -#. Create a Flask app with Flask 3.1.3. +#. Create a Flask app with Flask |conf_flask_version|. #. Use that to create a rock with Rockcraft. #. Use that to create a charm with Charmcraft. #. Use that to test, deploy, configure, etc., your Flask app on a local diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 000000000..f68dac4a5 --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,12 @@ +[[PackageOverrides]] +name = "python-apt" +ignore = true +reason = "System package" + +[[IgnoredVulns]] +id = "PYSEC-2026-1805" +reason = "Unfixable due to protobuf <6.33 pin for s390x segfault (https://github.com/protocolbuffers/protobuf/issues/24103)" + +[[IgnoredVulns]] +id = "GHSA-7gcm-g887-7qv7" +reason = "Unfixable due to protobuf <6.33 pin for s390x segfault (https://github.com/protocolbuffers/protobuf/issues/24103)" diff --git a/pyproject.toml b/pyproject.toml index a8acd8d55..1cce982c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ dynamic = ["version"] description = "The main tool to build, upload, and develop in general the Juju charms." readme = "README.md" dependencies = [ - "craft-application~=7.0", + "craft-application~=7.1", "craft-cli~=3.4", - "craft-grammar>=2.0.0", - "craft-parts~=2.20", - "craft-providers>=3.4.0,<4.0", - "craft-platforms~=0.10", - "craft-store~=3.2", + "craft-grammar>=2.3.0", + "craft-parts~=2.35", + "craft-providers>=3.7.1,<4.0", + "craft-platforms~=0.11", + "craft-store~=3.4", "distro>=1.7.0", "docker>=7.0.0", "humanize>=2.6.0", diff --git a/spread.yaml b/spread.yaml index 0bc27741a..6ce41c4f6 100644 --- a/spread.yaml +++ b/spread.yaml @@ -24,35 +24,64 @@ exclude: - snap backends: - google: - key: '$(HOST: echo "$SPREAD_GOOGLE_KEY")' - location: snapd-spread/us-east1-b + openstack: + key: '$(HOST: echo "$OS_CREDENTIALS_AMD64_PS7")' + plan: shared.medium halt-timeout: 3h - systems: + wait-timeout: 10m + proxy: ingress-haproxy.ps7.canonical.com + cidr-port-rel: [10.151.96.0/21:5000] + volume-auto-delete: true + environment: &openstack-env + SNAPD_USE_PROXY: "true" + HTTP_PROXY: "http://egress.ps7.internal:3128" + http_proxy: "http://egress.ps7.internal:3128" + HTTPS_PROXY: "http://egress.ps7.internal:3128" + https_proxy: "http://egress.ps7.internal:3128" + no_proxy: "127.0.0.1,127.0.0.53,localhost,10.0.0.0/8" + NO_PROXY: "127.0.0.1,127.0.0.53,localhost,10.0.0.0/8" + NTP_SERVER: "ntp.ps7.internal" + systems: &openstack-systems - ubuntu-18.04-64: + image: snapd-spread/ubuntu-18.04-64 workers: 1 storage: 40G - ubuntu-20.04-64: + image: snapd-spread/ubuntu-20.04-64-hwe workers: 3 storage: 40G - ubuntu-22.04-64: + image: snapd-spread/ubuntu-22.04-64 workers: 6 memory: 8G storage: 40G - ubuntu-24.04-64: + image: snapd-spread/ubuntu-24.04-64 workers: 6 memory: 8G storage: 40G - ubuntu-25.10-64: - image: ubuntu-os-cloud/ubuntu-2510-amd64 + image: snapd-spread/ubuntu-25.10-64 workers: 6 memory: 8G storage: 40G - ubuntu-26.04-64: - image: ubuntu-os-cloud-devel/ubuntu-2604-lts-amd64 + image: snapd-spread/ubuntu-26.04-64 workers: 6 memory: 8G storage: 40G + openstack-dev: + type: openstack + key: '$(HOST: echo "$OS_CREDENTIALS_STG_AMD64_PS7")' + plan: shared.xsmall + halt-timeout: 3h + wait-timeout: 10m + groups: [default] + proxy: ingress-haproxy.ps7.canonical.com + cidr-port-rel: [10.151.54.0/24:4000] + volume-auto-delete: true + environment: *openstack-env + systems: *openstack-systems ci: type: adhoc systems: @@ -124,6 +153,15 @@ backends: prepare: | set -e + #shellcheck source=tests/spread/tools.sh + . "$PROJECT_PATH/tests/spread/tools.sh" + if [ -n "${HTTP_PROXY:-}" ]; then + # The snapd daemon doesn't see the environment variables, so we must modify its config + # to use a proxy with it. Came from: + # https://github.com/canonical/snapd/blob/619cc925793591348db21bb45fa4af65288d2c42/tests/lib/prepare.sh#L90 + tools.setup_snapd_proxy + fi + # if the 'tools' directory inside the submodule does not exist, then assume the submodule is empty if [[ ! -d "$SNAPD_TESTING_TOOLS" ]]; then echo "Cannot run spread because submodule 'snapd-testing-tools' is empty. Fetch with 'git submodule update --init' and rerun spread." @@ -147,6 +185,7 @@ prepare: | tests.pkgs install snapd tests.pkgs install unzip + tests.pkgs install expect snap wait system seed.loaded @@ -157,6 +196,10 @@ prepare: | [ -e /usr/bin/lxd ] && apt-get remove --purge --yes lxd lxd-client lxcfs liblxc1 snap install lxd + if [[ "${SPREAD_SYSTEM:-}" == ubuntu-26.04-* ]]; then + snap refresh lxd --channel=6/stable + fi + # Hold snap refreshes for 24h. snap set system refresh.hold="$(date --date=tomorrow +%Y-%m-%dT%H:%M:%S%:z)" if ! snap watch --last=auto-refresh?; then @@ -169,6 +212,17 @@ prepare: | lxd waitready --timeout=30 lxd init --auto + if [ -n "${HTTP_PROXY:-}" ]; then + # Lxd either can't access or doesn't fallback to the environment variables, + # so we must set them. This is needed to download remote images. + lxc config set core.proxy_http $HTTP_PROXY + lxc config set core.proxy_https $HTTPS_PROXY + lxc config set core.proxy_ignore_hosts $NO_PROXY + lxc profile set default environment.http_proxy $HTTP_PROXY + lxc profile set default environment.https_proxy $HTTPS_PROXY + lxc profile set default environment.no_proxy $NO_PROXY + fi + #shellcheck source=tests/spread/tools/prepare.sh . "/charmcraft/tests/spread/tools/prepare.sh" @@ -264,7 +318,7 @@ suites: summary: test charm dependencies systems: - ubuntu-22.04-64 - kill-timeout: 60m # Setting up Juju can take a while. + kill-timeout: 3h # Setting up Juju can take a while. priority: 50 # Because setting up Juju takes a while, do these first. prepare: | snap install juju --channel=3/stable @@ -273,7 +327,13 @@ suites: mkdir -p ~/.local/share # Workaround for Juju not being able to create the directory if ! juju controllers | grep -q lxd; then - juju bootstrap localhost lxd + if [ -n "${HTTP_PROXY:-}" ]; then + juju bootstrap localhost lxd \ + --config apt-http-proxy="$HTTP_PROXY" \ + --config apt-https-proxy="$HTTPS_PROXY" + else + juju bootstrap localhost lxd + fi fi prepare-each: | if [[ -v JUJU_MODEL ]] && ! juju add-model "${JUJU_MODEL}"; then @@ -297,6 +357,19 @@ suites: tests/spread/store/: prepare: | snap install docker + + if [ -n "${HTTP_PROXY:-}" ]; then + mkdir -p /etc/systemd/system/snap.docker.dockerd.service.d + cat > /etc/systemd/system/snap.docker.dockerd.service.d/proxy.conf << EOF + [Service] + Environment="HTTP_PROXY=${HTTP_PROXY}" + Environment="HTTPS_PROXY=${HTTPS_PROXY}" + Environment="NO_PROXY=${NO_PROXY}" + EOF + systemctl daemon-reload + snap restart docker + fi + # make sure docker is working retry -n 10 --wait 2 sh -c 'docker run --rm hello-world' # https://linuxcontainers.org/lxd/docs/master/howto/network_bridge_firewalld/#prevent-issues-with-lxd-and-docker @@ -305,7 +378,9 @@ suites: # Ensure that the reused charms are registered if necessary. if ! charmcraft status "${CHARM_DEFAULT_NAME}"; then - charmcraft register $CHARM_DEFAULT_NAME + # Another system may register the charm simultaneously. If registration fails + # because we lost the race, verify that the charm is already registered. + charmcraft register "${CHARM_DEFAULT_NAME}" || charmcraft status "${CHARM_DEFAULT_NAME}" fi rm -f charmcraft.yaml @@ -326,6 +401,11 @@ suites: CHARMCRAFT_AUTH: "$(HOST: echo $CHARMCRAFT_AUTH)" CHARMCRAFT_SINGLE_CHARM_AUTH: "$(HOST: echo $CHARMCRAFT_SINGLE_CHARM_AUTH)" + # Ubuntu One staging SSO credentials for testing the login flow. + CHARMCRAFT_STORE_SSO_URL: "$(HOST: echo ${CHARMCRAFT_STORE_SSO_URL:-https://login.staging.ubuntu.com})" + STAGING_SSO_USERNAME: "$(HOST: echo $STAGING_SSO_USERNAME)" + STAGING_SSO_PASSWORD: "$(HOST: echo $STAGING_SSO_PASSWORD)" + # to not flood Charmhub with names the same two are always used in the Store related # tests (except in the names registration tests, of course); register them manually # in staging Charmhub authenticating with the configured credentials diff --git a/tests/commands/test_store_api.py b/tests/commands/test_store_api.py index e77bd3b57..f73e386bb 100644 --- a/tests/commands/test_store_api.py +++ b/tests/commands/test_store_api.py @@ -298,7 +298,7 @@ def test_auth_bad_credentials(charmhub_config, monkeypatch): def test_no_keyring(charmhub_config): """Verify CraftStore is raised from Store when no keyring is available.""" with patch( - "craft_store.StoreClient.__init__", + "craft_store.Auth.__init__", side_effect=craft_store.errors.NoKeyringError(), ): with pytest.raises(CraftError) as error: diff --git a/tests/commands/test_store_client.py b/tests/commands/test_store_client.py index ed195f76d..235e75f81 100644 --- a/tests/commands/test_store_client.py +++ b/tests/commands/test_store_client.py @@ -19,7 +19,7 @@ import base64 import json from unittest import mock -from unittest.mock import call, patch +from unittest.mock import MagicMock, call, patch import craft_store import pytest @@ -93,12 +93,12 @@ def text(self): @pytest.fixture def client_class(): - """Return a client instance with craft-store's StoreClient methods mocked.""" + """Return a client instance with craft-store's UbuntuOneStoreClient methods mocked.""" auth_patch = patch("craft_store.Auth.__init__", return_value=None) auth_patch.start() - class _StoreClientMock(craft_store.StoreClient): + class _StoreClientMock(craft_store.UbuntuOneStoreClient): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -128,13 +128,15 @@ def test_client_init(): api_url = "http://api.test" storage_url = "http://storage.test" user_agent = "Super User Agent" - with patch("craft_store.StoreClient.__init__") as mock_client_init: + with patch("craft_store.UbuntuOneStoreClient.__init__") as mock_client_init: + mock_client_init.return_value = None with patch("charmcraft.store.client.build_user_agent") as mock_ua: mock_ua.return_value = user_agent Client(api_url, storage_url, user_agent=user_agent) mock_client_init.assert_called_with( base_url=api_url, storage_base_url=storage_url, + auth_url="https://login.ubuntu.com", endpoints=craft_store.endpoints.CHARMHUB, application_name="charmcraft", user_agent=user_agent, @@ -218,6 +220,20 @@ def test_client_init_removes_trailing_slashes(client_class): assert client.storage_base_url == "http://storage.test" +def test_get_authorization_header_with_cached_store_token(client_class): + """Authorization header is built from a cached store token.""" + cached_token = "some-plain-store-token" + client = client_class( + "https://api.charmhub.io", "https://storage.snapcraftcontent.com" + ) + client._auth = MagicMock() + client._auth.get_credentials.return_value = cached_token + + header = client._get_authorization_header() + + assert header == f"Macaroon {cached_token}" + + def test_client_push_simple_ok(tmp_path, emitter, client_class): """Happy path for pushing bytes.""" # fake some bytes to push diff --git a/tests/conftest.py b/tests/conftest.py index 17c64094a..c73690a14 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -169,7 +169,11 @@ def mock_store_anonymous_client() -> mock.Mock: @pytest.fixture def mock_publisher_gateway() -> mock.Mock: - return mock.Mock(spec_set=craft_store.PublisherGateway) + gateway = mock.Mock(spec_set=craft_store.PublisherGateway) + gateway.whoami.return_value = { + "account": {"username": "test-user"}, + } + return gateway @pytest.fixture diff --git a/tests/extensions/test_app.py b/tests/extensions/test_app.py index 6854ff5eb..5ee27dd1b 100644 --- a/tests/extensions/test_app.py +++ b/tests/extensions/test_app.py @@ -18,15 +18,19 @@ import pytest -from charmcraft import extensions +from charmcraft import errors, extensions from charmcraft.errors import ExtensionError from charmcraft.extensions.app import ( - DjangoFramework, - ExpressJSFramework, - FastAPIFramework, - FlaskFramework, - GoFramework, - SpringBootFramework, + DjangoFrameworkV1, + ExpressJSFrameworkFactory, + ExpressJSFrameworkV1, + FastAPIFrameworkFactory, + FastAPIFrameworkV1, + FlaskFrameworkFactory, + FlaskFrameworkV1, + GoFrameworkFactory, + GoFrameworkV1, + SpringBootFrameworkV1, ) NON_OPTIONAL_OPTIONS = { @@ -77,7 +81,7 @@ def make_spring_boot_input_yaml(): make_flask_input_yaml(), False, { - "actions": FlaskFramework.actions, + "actions": FlaskFrameworkV1.actions, "assumes": ["k8s-api"], "bases": [{"channel": "22.04", "name": "ubuntu"}], "containers": { @@ -103,7 +107,7 @@ def make_spring_boot_input_yaml(): ], "config": { "options": { - **FlaskFramework.options, + **FlaskFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], }, }, @@ -154,7 +158,7 @@ def make_spring_boot_input_yaml(): }, False, { - "actions": DjangoFramework.actions, + "actions": DjangoFrameworkV1.actions, "assumes": ["k8s-api"], "base": "ubuntu@22.04", "platforms": { @@ -188,7 +192,7 @@ def make_spring_boot_input_yaml(): ], "config": { "options": { - **DjangoFramework.options, + **DjangoFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], }, }, @@ -232,9 +236,9 @@ def make_spring_boot_input_yaml(): "extensions": ["go-framework"], "config": NON_OPTIONAL_OPTIONS, }, - True, + False, { - "actions": GoFramework.actions, + "actions": GoFrameworkV1.actions, "assumes": ["k8s-api"], "base": "ubuntu@24.04", "platforms": { @@ -263,7 +267,7 @@ def make_spring_boot_input_yaml(): ], "config": { "options": { - **GoFramework.options, + **GoFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], }, }, @@ -307,9 +311,9 @@ def make_spring_boot_input_yaml(): "extensions": ["fastapi-framework"], "config": NON_OPTIONAL_OPTIONS, }, - True, + False, { - "actions": FastAPIFramework.actions, + "actions": FastAPIFrameworkV1.actions, "assumes": ["k8s-api"], "base": "ubuntu@24.04", "platforms": { @@ -338,7 +342,7 @@ def make_spring_boot_input_yaml(): ], "config": { "options": { - **FastAPIFramework.options, + **FastAPIFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], }, }, @@ -382,9 +386,9 @@ def make_spring_boot_input_yaml(): "extensions": ["expressjs-framework"], "config": NON_OPTIONAL_OPTIONS, }, - True, + False, { - "actions": ExpressJSFramework.actions, + "actions": ExpressJSFrameworkV1.actions, "assumes": ["k8s-api"], "base": "ubuntu@24.04", "platforms": { @@ -413,7 +417,7 @@ def make_spring_boot_input_yaml(): ], "config": { "options": { - **ExpressJSFramework.options, + **ExpressJSFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], }, }, @@ -448,7 +452,7 @@ def make_spring_boot_input_yaml(): make_spring_boot_input_yaml(), True, { - "actions": SpringBootFramework.actions, + "actions": SpringBootFrameworkV1.actions, "assumes": ["k8s-api"], "base": "ubuntu@24.04", "platforms": { @@ -477,7 +481,7 @@ def make_spring_boot_input_yaml(): ], "config": { "options": { - **SpringBootFramework.options, + **SpringBootFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], }, }, @@ -520,6 +524,395 @@ def test_apply_extensions_correct( assert applied == expected +def test_go_framework_26_04_uses_v2_snippet(monkeypatch, tmp_path): + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-go-v2", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["go-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + assert applied["containers"] == {"app": {"resource": "app-image"}} + assert applied["resources"] == { + "app-image": { + "type": "oci-image", + "description": "go application image.", + } + } + assert applied["parts"]["charm"] == { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + } + + +def test_flask_framework_26_04_uses_v2_snippet(monkeypatch, tmp_path): + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-flask-v2", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["flask-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + assert applied["containers"] == {"app": {"resource": "app-image"}} + assert applied["resources"] == { + "app-image": { + "type": "oci-image", + "description": "flask application image.", + } + } + assert applied["parts"]["charm"] == { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + } + + +def test_django_framework_26_04_uses_v2_snippet(monkeypatch, tmp_path): + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-django-v2", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["django-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + assert applied["containers"] == {"app": {"resource": "app-image"}} + assert applied["resources"] == { + "app-image": { + "type": "oci-image", + "description": "django application image.", + } + } + assert applied["parts"]["charm"] == { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + } + + +def test_fastapi_framework_26_04_uses_v2_snippet(monkeypatch, tmp_path): + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-fastapi-v2", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["fastapi-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + assert applied["containers"] == {"app": {"resource": "app-image"}} + assert applied["resources"] == { + "app-image": { + "type": "oci-image", + "description": "fastapi application image.", + } + } + assert applied["parts"]["charm"] == { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + } + + +def test_expressjs_framework_26_04_uses_v2_snippet(monkeypatch, tmp_path): + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-expressjs-v2", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["expressjs-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + assert applied["containers"] == {"app": {"resource": "app-image"}} + assert applied["resources"] == { + "app-image": { + "type": "oci-image", + "description": "expressjs application image.", + } + } + assert applied["parts"]["charm"] == { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + } + + +def test_spring_boot_framework_26_04_uses_v2_snippet(monkeypatch, tmp_path): + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-springboot-v2", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["spring-boot-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + assert applied["containers"] == {"app": {"resource": "app-image"}} + assert applied["resources"] == { + "app-image": { + "type": "oci-image", + "description": "spring-boot application image.", + } + } + assert applied["parts"]["charm"] == { + "plugin": "uv", + "source": ".", + "build-snaps": ["astral-uv", "rustup"], + "override-build": "rustup default stable\ncraftctl default", + "uv-groups": ["charmlibs-pydeps"], + } + + +def test_go_framework_platforms_only_routes_to_v2(monkeypatch, tmp_path): + """Test that go on 26.04 via platforms (no top-level base) routes to V2 (defect 3 fix).""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-go-platforms-v2", + "summary": "test summary", + "description": "test description", + "platforms": {"ubuntu@26.04:amd64": None}, + "extensions": ["go-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + # V2 uses uv plugin and different image name + assert applied["parts"]["charm"]["plugin"] == "uv" + + +def test_go_framework_24_04_still_routes_to_v1(monkeypatch, tmp_path): + """Test that go on 24.04 still routes to V1 with charm plugin.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-go-v1", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@24.04", + "platforms": {"amd64": None}, + "extensions": ["go-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + # V1 uses charm plugin + assert applied["parts"]["charm"]["plugin"] == "charm" + + +def test_12factor_extension_rejects_multi_base(monkeypatch, tmp_path): + """Test that 12-factor extensions reject projects with multiple bases.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-multibase", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@24.04", + "platforms": {"ubuntu@26.04:amd64": None}, + "extensions": ["flask-framework"], + } + + with pytest.raises( + errors.ExtensionError, + match="does not support multiple bases", + ): + extensions.apply_extensions(tmp_path, input_yaml) + + +def test_v2_extension_experimental_gating_enforced(monkeypatch, tmp_path): + """Test that V2 extensions require CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS env var.""" + monkeypatch.delenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", raising=False) + input_yaml = { + "type": "charm", + "name": "test-flask-v2-no-env", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["flask-framework"], + } + + with pytest.raises( + errors.ExtensionError, + match=".*experimental on base.*ubuntu@26.04.*", + ): + extensions.apply_extensions(tmp_path, input_yaml) + + +def test_v2_extension_experimental_gating_passes_with_env(monkeypatch, tmp_path): + """Test that V2 extensions work with CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS env var.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-flask-v2-with-env", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["flask-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + # Should succeed and produce V2 output + assert applied["parts"]["charm"]["plugin"] == "uv" + + +def test_flask_framework_factory_get_supported_bases_no_duplicates(): + """Test that flask factory supported bases are deduped (defect 1 fix).""" + bases = FlaskFrameworkFactory.get_supported_bases() + assert bases == [("ubuntu", "22.04"), ("ubuntu", "24.04"), ("ubuntu", "26.04")] + assert len(bases) == len(set(bases)), "Supported bases should not have duplicates" + + +def test_go_framework_factory_is_experimental_correct(monkeypatch): + """Test that go factory is_experimental delegates to correct class per base (defect 2 fix).""" + # Go V1 on 24.04 is now stable (not experimental) + assert GoFrameworkFactory.is_experimental(("ubuntu", "24.04")) is False + # Go V2 on 26.04 should be experimental + assert GoFrameworkFactory.is_experimental(("ubuntu", "26.04")) is True + + +def test_fastapi_framework_factory_is_experimental_24_04(monkeypatch): + """Test that fastapi on 24.04 is stable (was experimental, now GA).""" + # FastAPI V1 on 24.04 is now stable + assert FastAPIFrameworkFactory.is_experimental(("ubuntu", "24.04")) is False + + +def test_expressjs_framework_factory_is_experimental_24_04(monkeypatch): + """Test that expressjs on 24.04 is stable (was experimental, now GA).""" + # ExpressJS V1 on 24.04 is now stable + assert ExpressJSFrameworkFactory.is_experimental(("ubuntu", "24.04")) is False + + +def test_v2_check_input_rejects_non_charm_type(monkeypatch, tmp_path): + """Test that V2 _check_input validates type == charm.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "bundle", # Invalid: not a charm + "name": "test-bundle", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["flask-framework"], + } + + with pytest.raises( + errors.ExtensionError, + match=".*incompatible with type 'bundle'", + ): + extensions.apply_extensions(tmp_path, input_yaml) + + +def test_v2_check_input_rejects_customized_charm_part(monkeypatch, tmp_path): + """Test that V2 _check_input rejects customized charm part.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + input_yaml = { + "type": "charm", + "name": "test-custom-charm", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "parts": {"charm": {"plugin": "dump", "source": "."}}, # Custom + "extensions": ["flask-framework"], + } + + with pytest.raises( + errors.ExtensionError, + match=".*incompatible with customized charm part", + ): + extensions.apply_extensions(tmp_path, input_yaml) + + +def test_v2_paas_config_part_present(monkeypatch, tmp_path): + """Test that V2 generates paas-config dump part when paas-config.yaml exists.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + + # Create paas-config.yaml in the project + (tmp_path / "paas-config.yaml").write_text("key: value\n") + + input_yaml = { + "type": "charm", + "name": "test-paas-config", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["flask-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + # V2 should include config part for paas-config.yaml + assert "config" in applied["parts"] + assert applied["parts"]["config"]["plugin"] == "dump" + assert applied["parts"]["config"]["stage"] == ["paas-config.yaml"] + + +def test_v2_paas_config_part_absent_when_no_file(monkeypatch, tmp_path): + """Test that V2 does not generate paas-config part when file is absent.""" + monkeypatch.setenv("CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") + + input_yaml = { + "type": "charm", + "name": "test-no-paas-config", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "extensions": ["flask-framework"], + } + + applied = extensions.apply_extensions(tmp_path, input_yaml) + + # V2 should not include config part when paas-config.yaml is absent + assert "config" not in applied["parts"] + + PROTECTED_FIELDS_TEST_PARAMETERS = [ pytest.param({"type": "bundle"}, id="type"), pytest.param({"containers": {"foobar": {"resource": "foobar"}}}, id="containers"), @@ -541,7 +934,7 @@ def test_flask_merge_options(flask_input_yaml, tmp_path): applied = extensions.apply_extensions(tmp_path, flask_input_yaml) assert applied["config"] == { "options": { - **FlaskFramework.options, + **FlaskFrameworkV1.options, **added_options, } } @@ -551,7 +944,7 @@ def test_flask_merge_action(flask_input_yaml, tmp_path): added_actions = {"foobar": {}} flask_input_yaml["actions"] = added_actions applied = extensions.apply_extensions(tmp_path, flask_input_yaml) - assert applied["actions"] == {**FlaskFramework.actions, **added_actions} + assert applied["actions"] == {**FlaskFrameworkV1.actions, **added_actions} def test_flask_merge_relation(flask_input_yaml, tmp_path): @@ -576,7 +969,7 @@ def test_flask_merge_charm_libs(flask_input_yaml, tmp_path): added_charm_libs = [{"lib": "smtp_integrator.smtp", "version": "0"}] flask_input_yaml["charm-libs"] = added_charm_libs applied = extensions.apply_extensions(tmp_path, flask_input_yaml) - assert applied["charm-libs"] == [*FlaskFramework._CHARM_LIBS, *added_charm_libs] + assert applied["charm-libs"] == [*FlaskFrameworkV1._CHARM_LIBS, *added_charm_libs] INCOMPATIBLE_FIELDS_TEST_PARAMETERS = [ @@ -687,6 +1080,46 @@ def test_handle_charm_part_adds_part(flask_input_yaml, tmp_path): } +def test_invalid_paas_config_yaml_fails_pack(flask_input_yaml, tmp_path): + (tmp_path / "paas-config.yaml").write_text( + "framework_logging_format: [json", encoding="utf-8" + ) + + with pytest.raises(ExtensionError, match=r"invalid YAML in paas-config.yaml"): + extensions.apply_extensions(tmp_path, flask_input_yaml) + + +def test_json_framework_logging_disallowed_framework_fails_pack(tmp_path): + go_input_yaml = { + "type": "charm", + "name": "test-go", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@24.04", + "platforms": { + "amd64": None, + }, + "extensions": ["go-framework"], + "config": NON_OPTIONAL_OPTIONS, + } + (tmp_path / "paas-config.yaml").write_text( + "framework_logging_format: json\n", encoding="utf-8" + ) + + with pytest.raises( + ExtensionError, + match=r"framework_logging_format: json in paas-config.yaml is not supported for 'go-framework'", + ): + extensions.apply_extensions(tmp_path, go_input_yaml) + + +def test_json_framework_logging_supported_framework_passes(flask_input_yaml, tmp_path): + (tmp_path / "paas-config.yaml").write_text( + "framework_logging_format: json\n", encoding="utf-8" + ) + extensions.apply_extensions(tmp_path, flask_input_yaml) + + @pytest.mark.parametrize( ("input_yaml", "requires", "expected_options"), [ @@ -694,7 +1127,7 @@ def test_handle_charm_part_adds_part(flask_input_yaml, tmp_path): make_flask_input_yaml(), {"oidc-foobar": {"interface": "oauth"}}, { - **FlaskFramework.options, + **FlaskFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], "oidc-foobar-redirect-path": { "type": "string", @@ -713,7 +1146,7 @@ def test_handle_charm_part_adds_part(flask_input_yaml, tmp_path): make_spring_boot_input_yaml(), {"oidc-foobar": {"interface": "oauth"}}, { - **SpringBootFramework.options, + **SpringBootFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], "oidc-foobar-redirect-path": { "type": "string", @@ -741,7 +1174,7 @@ def test_handle_charm_part_adds_part(flask_input_yaml, tmp_path): "other-oidc": {"interface": "oauth"}, }, { - **FlaskFramework.options, + **FlaskFrameworkV1.options, **NON_OPTIONAL_OPTIONS["options"], "oidc-foobar-redirect-path": { "type": "string", diff --git a/tests/extensions/test_registry.py b/tests/extensions/test_registry.py index e7ddaf472..5b1cfc030 100644 --- a/tests/extensions/test_registry.py +++ b/tests/extensions/test_registry.py @@ -18,6 +18,14 @@ import pytest from charmcraft import errors, extensions +from charmcraft.extensions.app import ( + DjangoFrameworkFactory, + ExpressJSFrameworkFactory, + FastAPIFrameworkFactory, + FlaskFrameworkFactory, + GoFrameworkFactory, + SpringBootFrameworkFactory, +) from charmcraft.extensions.extension import Extension @@ -110,3 +118,91 @@ def test_unregister(fake_extensions): extensions.unregister(FakeExtension1.name) with pytest.raises(errors.ExtensionError): extensions.get_extension_class(FakeExtension1.name) + + +def test_real_framework_factories_no_duplicate_experimental_bases(): + """Verify real framework factories have no duplicate experimental_bases (defect 1 fix).""" + # Import the actual factories directly + factories = [ + ("flask-framework", FlaskFrameworkFactory), + ("django-framework", DjangoFrameworkFactory), + ("go-framework", GoFrameworkFactory), + ("fastapi-framework", FastAPIFrameworkFactory), + ("expressjs-framework", ExpressJSFrameworkFactory), + ("spring-boot-framework", SpringBootFrameworkFactory), + ] + + for name, factory in factories: + bases = factory.get_supported_bases() + # Verify no duplicates + assert len(bases) == len(set(bases)), ( + f"{name} has duplicate supported_bases: {bases}" + ) + + +def test_real_framework_factories_experimental_status_correct(): + """Verify experimental status matches framework requirements (defect 2 fix).""" + # Map factories to expected experimental status per base + test_cases = [ + ( + "flask", + FlaskFrameworkFactory, + [ + (("ubuntu", "22.04"), False), # V1: stable + (("ubuntu", "26.04"), True), # V2: experimental + ], + ), + ( + "django", + DjangoFrameworkFactory, + [ + (("ubuntu", "22.04"), False), # V1: stable + (("ubuntu", "26.04"), True), # V2: experimental + ], + ), + ( + "go", + GoFrameworkFactory, + [ + (("ubuntu", "24.04"), False), # V1: stable + (("ubuntu", "26.04"), True), # V2: experimental + ], + ), + ( + "fastapi", + FastAPIFrameworkFactory, + [ + ( + ("ubuntu", "24.04"), + False, + ), # V1: stable + (("ubuntu", "26.04"), True), # V2: experimental + ], + ), + ( + "expressjs", + ExpressJSFrameworkFactory, + [ + ( + ("ubuntu", "24.04"), + False, + ), # V1: stable + (("ubuntu", "26.04"), True), # V2: experimental + ], + ), + ( + "spring-boot", + SpringBootFrameworkFactory, + [ + (("ubuntu", "24.04"), False), # V1: stable + (("ubuntu", "26.04"), True), # V2: experimental + ], + ), + ] + + for name, factory, assertions in test_cases: + for base, expected_experimental in assertions: + actual = factory.is_experimental(base) + assert actual == expected_experimental, ( + f"{name} on {base}: expected is_experimental={expected_experimental}, got {actual}" + ) diff --git a/tests/integration/commands/test_init.py b/tests/integration/commands/test_init.py index 799e97fa3..e56e69500 100644 --- a/tests/integration/commands/test_init.py +++ b/tests/integration/commands/test_init.py @@ -48,6 +48,8 @@ "README.md", "src", "src/charm.py", + "src/grafana_dashboards", + "src/grafana_dashboards/overview.json", "tests", "tests/integration", "tests/integration/conftest.py", diff --git a/tests/integration/commands/test_store_commands.py b/tests/integration/commands/test_store_commands.py index a04577c61..088c46853 100644 --- a/tests/integration/commands/test_store_commands.py +++ b/tests/integration/commands/test_store_commands.py @@ -28,7 +28,10 @@ from charmcraft import errors, utils from charmcraft.application.commands import FetchLibCommand -from charmcraft.application.commands.store import CreateTrack +from charmcraft.application.commands.store import ( + CHARMLIBS_DEPRECATION_WARNING, + CreateTrack, +) from tests import factory OPERATOR_LIBS_LINUX_APT_ID = "7c3dbc9c2ad44a47bd6fcb25caa270e5" @@ -52,6 +55,7 @@ def test_fetchlib_simple_downloaded( ) FetchLibCommand(config).run(args) + emitter.assert_progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) assert saved_file.exists() message = emitter.interactions[-1].args[1] diff --git a/tests/integration/invalid-charms/multibase-charm-plugin/charmcraft.yaml b/tests/integration/invalid-charms/multibase-charm-plugin/charmcraft.yaml index 0b25d11f0..ec59ac38a 100644 --- a/tests/integration/invalid-charms/multibase-charm-plugin/charmcraft.yaml +++ b/tests/integration/invalid-charms/multibase-charm-plugin/charmcraft.yaml @@ -10,9 +10,6 @@ platforms: questing: build-on: ubuntu@25.10:amd64 build-for: ubuntu@25.10:amd64 - resolute: - build-on: ubuntu@26.04:amd64 - build-for: ubuntu@26.04:amd64 parts: my-part: diff --git a/tests/integration/invalid-charms/multibase-resolute-charm-plugin/errors.json b/tests/integration/invalid-charms/multibase-resolute-charm-plugin/errors.json index b016b7e19..8941bc0fd 100644 --- a/tests/integration/invalid-charms/multibase-resolute-charm-plugin/errors.json +++ b/tests/integration/invalid-charms/multibase-resolute-charm-plugin/errors.json @@ -4,7 +4,7 @@ "loc": [ "parts" ], - "msg": "Value error, Cannot use 'charm' plugin with base 'ubuntu@26.04'", + "msg": "Value error, Cannot use 'charm' or 'reactive' plugins with base 'ubuntu@26.04'", "input": { "my-part": { "plugin": "charm", @@ -12,7 +12,7 @@ } }, "ctx": { - "error": "Cannot use 'charm' plugin with base 'ubuntu@26.04'" + "error": "Cannot use 'charm' or 'reactive' plugins with base 'ubuntu@26.04'" } } ] diff --git a/tests/integration/invalid-charms/resolute-charm-plugin/errors.json b/tests/integration/invalid-charms/resolute-charm-plugin/errors.json index b016b7e19..8941bc0fd 100644 --- a/tests/integration/invalid-charms/resolute-charm-plugin/errors.json +++ b/tests/integration/invalid-charms/resolute-charm-plugin/errors.json @@ -4,7 +4,7 @@ "loc": [ "parts" ], - "msg": "Value error, Cannot use 'charm' plugin with base 'ubuntu@26.04'", + "msg": "Value error, Cannot use 'charm' or 'reactive' plugins with base 'ubuntu@26.04'", "input": { "my-part": { "plugin": "charm", @@ -12,7 +12,7 @@ } }, "ctx": { - "error": "Cannot use 'charm' plugin with base 'ubuntu@26.04'" + "error": "Cannot use 'charm' or 'reactive' plugins with base 'ubuntu@26.04'" } } ] diff --git a/tests/integration/sample-charms/platforms-noble-charm/charmcraft.yaml b/tests/integration/sample-charms/platforms-noble-charm/charmcraft.yaml new file mode 100644 index 000000000..bef908014 --- /dev/null +++ b/tests/integration/sample-charms/platforms-noble-charm/charmcraft.yaml @@ -0,0 +1,12 @@ +name: example-charm +summary: An example charm with platforms using charm plugin +description: | + A description for an example charm with platforms using the charm plugin. +type: charm +base: ubuntu@24.04 +platforms: + amd64: + +parts: + charm: + plugin: charm diff --git a/tests/integration/sample-charms/platforms-noble-charm/expected.yaml b/tests/integration/sample-charms/platforms-noble-charm/expected.yaml new file mode 100644 index 000000000..d5df1451f --- /dev/null +++ b/tests/integration/sample-charms/platforms-noble-charm/expected.yaml @@ -0,0 +1,16 @@ +name: example-charm +summary: An example charm with platforms using charm plugin +description: | + A description for an example charm with platforms using the charm plugin. +base: ubuntu@24.04 +platforms: + amd64: + build-on: + - amd64 + build-for: + - amd64 +parts: + charm: + plugin: charm + source: . +type: charm diff --git a/tests/integration/sample-charms/platforms-resolute-reactive/charmcraft.yaml b/tests/integration/sample-charms/platforms-noble-reactive/charmcraft.yaml similarity index 91% rename from tests/integration/sample-charms/platforms-resolute-reactive/charmcraft.yaml rename to tests/integration/sample-charms/platforms-noble-reactive/charmcraft.yaml index c5ec3373f..93a97a0a4 100644 --- a/tests/integration/sample-charms/platforms-resolute-reactive/charmcraft.yaml +++ b/tests/integration/sample-charms/platforms-noble-reactive/charmcraft.yaml @@ -3,7 +3,7 @@ summary: An example charm with platforms description: | A description for an example charm with platforms. type: charm -base: ubuntu@26.04 +base: ubuntu@24.04 platforms: amd64: diff --git a/tests/integration/sample-charms/platforms-resolute-reactive/expected.yaml b/tests/integration/sample-charms/platforms-noble-reactive/expected.yaml similarity index 93% rename from tests/integration/sample-charms/platforms-resolute-reactive/expected.yaml rename to tests/integration/sample-charms/platforms-noble-reactive/expected.yaml index 629f42c2d..9a26c8e87 100644 --- a/tests/integration/sample-charms/platforms-resolute-reactive/expected.yaml +++ b/tests/integration/sample-charms/platforms-noble-reactive/expected.yaml @@ -2,7 +2,7 @@ name: example-charm summary: An example charm with platforms description: | A description for an example charm with platforms. -base: ubuntu@26.04 +base: ubuntu@24.04 platforms: amd64: build-on: diff --git a/tests/spread/dependencies/requirements/requirements-hashes.txt b/tests/spread/dependencies/requirements/requirements-hashes.txt index e829c8ec8..2059322fb 100644 --- a/tests/spread/dependencies/requirements/requirements-hashes.txt +++ b/tests/spread/dependencies/requirements/requirements-hashes.txt @@ -7,57 +7,80 @@ pyxdg==0.28 \ --hash=sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4 \ --hash=sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab # via -r requirements-basic.txt -pyyaml==6.0.1 \ - --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ - --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ - --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ - --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ - --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ - --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ - --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \ - --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ - --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ - --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ - --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ - --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ - --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ - --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ - --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ - --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ - --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ - --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \ - --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ - --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ - --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ - --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ - --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ - --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ - --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ - --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ - --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ - --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ - --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ - --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ - --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \ - --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \ - --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \ - --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \ - --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \ - --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \ - --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \ - --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \ - --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ - --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ - --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ - --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ - --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ - --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ - --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ - --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ - --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ - --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ - --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ - --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f +PyYAML==6.0.3 \ + --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ + --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ + --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ + --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ + --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ + --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ + --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ + --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ + --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ + --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ + --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ + --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ + --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ + --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ + --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ + --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ + --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ + --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ + --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ + --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ + --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ + --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ + --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ + --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 # via ops websocket-client==1.6.3 \ --hash=sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f \ diff --git a/tests/spread/dependencies/strict-dependencies/charm/requirements.txt b/tests/spread/dependencies/strict-dependencies/charm/requirements.txt index 9b916d66c..e5dc20e28 100644 --- a/tests/spread/dependencies/strict-dependencies/charm/requirements.txt +++ b/tests/spread/dependencies/strict-dependencies/charm/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.1 -pyyaml==6.0.1 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/smoketests/reactive/reactivecharm/charmcraft.yaml b/tests/spread/smoketests/reactive-legacy/reactivecharm/charmcraft.yaml similarity index 74% rename from tests/spread/smoketests/reactive/reactivecharm/charmcraft.yaml rename to tests/spread/smoketests/reactive-legacy/reactivecharm/charmcraft.yaml index 98eefa708..8f600c30c 100644 --- a/tests/spread/smoketests/reactive/reactivecharm/charmcraft.yaml +++ b/tests/spread/smoketests/reactive-legacy/reactivecharm/charmcraft.yaml @@ -12,9 +12,8 @@ parts: plugin: reactive reactive-charm-build-arguments: - -v - - --binary-wheels-from-source - - --upgrade-buildvenv-core-deps - - --ignore-requires-python + # Use binary wheels to speed up the build and avoid building dependencies from source + - --binary-wheels build-snaps: - charm/CHARM_SNAP_CHANNEL build-packages: [python3-dev] diff --git a/tests/spread/smoketests/reactive/reactivecharm/config.yaml b/tests/spread/smoketests/reactive-legacy/reactivecharm/config.yaml similarity index 100% rename from tests/spread/smoketests/reactive/reactivecharm/config.yaml rename to tests/spread/smoketests/reactive-legacy/reactivecharm/config.yaml diff --git a/tests/spread/smoketests/reactive/reactivecharm/layer.yaml b/tests/spread/smoketests/reactive-legacy/reactivecharm/layer.yaml similarity index 100% rename from tests/spread/smoketests/reactive/reactivecharm/layer.yaml rename to tests/spread/smoketests/reactive-legacy/reactivecharm/layer.yaml diff --git a/tests/spread/smoketests/reactive/reactivecharm/metadata.yaml b/tests/spread/smoketests/reactive-legacy/reactivecharm/metadata.yaml similarity index 100% rename from tests/spread/smoketests/reactive/reactivecharm/metadata.yaml rename to tests/spread/smoketests/reactive-legacy/reactivecharm/metadata.yaml diff --git a/tests/spread/smoketests/reactive/reactivecharm/reactive/reactivecharm.py b/tests/spread/smoketests/reactive-legacy/reactivecharm/reactive/reactivecharm.py similarity index 87% rename from tests/spread/smoketests/reactive/reactivecharm/reactive/reactivecharm.py rename to tests/spread/smoketests/reactive-legacy/reactivecharm/reactive/reactivecharm.py index f26647159..2ce29952b 100644 --- a/tests/spread/smoketests/reactive/reactivecharm/reactive/reactivecharm.py +++ b/tests/spread/smoketests/reactive-legacy/reactivecharm/reactive/reactivecharm.py @@ -6,7 +6,7 @@ def install_reactivecharm(): # Do your setup here. # # If your charm has other dependencies before it can install, - # add those as @when() clauses above., or as additional @when() + # add those as @when() clauses above, or as additional @when() # decorated handlers below # # See the following for information about reactive charms: diff --git a/tests/spread/smoketests/reactive-legacy/task.yaml b/tests/spread/smoketests/reactive-legacy/task.yaml new file mode 100644 index 000000000..eb5c2365e --- /dev/null +++ b/tests/spread/smoketests/reactive-legacy/task.yaml @@ -0,0 +1,37 @@ +summary: pack a simple reactive charm on older systems +priority: 100 + +systems: + - ubuntu-20.04-64 + - ubuntu-22.04-64 + +environment: + CHARM_SNAP_CHANNEL/stable: stable + CHARM_SNAP_CHANNEL/two: 2.x/stable + +prepare: | + cd reactivecharm + + BASE_CHANNEL=$(echo $SPREAD_SYSTEM | cut -d- -f2) + + sed -i "s|BASE_CHANNEL|$BASE_CHANNEL|g" charmcraft.yaml + sed -i "s|CHARM_SNAP_CHANNEL|$CHARM_SNAP_CHANNEL|g" charmcraft.yaml + + git init + git config --global --add safe.directory $PWD + git add . + git config user.email "you@example.com" + git config user.name "Your Name" + git commit -m 'initial commit' + +restore: | + pushd reactivecharm + charmcraft clean + popd + + rm -rf reactivecharm + +execute: | + cd reactivecharm + charmcraft pack + test -f reactive-test*.charm diff --git a/tests/spread/smoketests/reactive-resolute/reactivecharm/charmcraft.yaml b/tests/spread/smoketests/reactive-resolute/reactivecharm/charmcraft.yaml new file mode 100644 index 000000000..6b3d71ae2 --- /dev/null +++ b/tests/spread/smoketests/reactive-resolute/reactivecharm/charmcraft.yaml @@ -0,0 +1,15 @@ +type: "charm" +base: ubuntu@26.04 +platforms: + amd64: +parts: + charm: + source: . + plugin: reactive + reactive-charm-build-arguments: + - -v + # Use binary wheels to speed up the build and avoid building dependencies from source + - --binary-wheels + build-snaps: + - charm/latest/stable + build-packages: [python3-dev] diff --git a/tests/spread/smoketests/reactive-resolute/reactivecharm/config.yaml b/tests/spread/smoketests/reactive-resolute/reactivecharm/config.yaml new file mode 100644 index 000000000..29ed5a52f --- /dev/null +++ b/tests/spread/smoketests/reactive-resolute/reactivecharm/config.yaml @@ -0,0 +1,13 @@ +options: + string-option: + type: string + default: "Default Value" + description: "A short description of the configuration option" + boolean-option: + type: boolean + default: False + description: "A short description of the configuration option" + int-option: + type: int + default: 9001 + description: "A short description of the configuration option" diff --git a/tests/spread/smoketests/reactive-resolute/reactivecharm/layer.yaml b/tests/spread/smoketests/reactive-resolute/reactivecharm/layer.yaml new file mode 100644 index 000000000..102cf18c2 --- /dev/null +++ b/tests/spread/smoketests/reactive-resolute/reactivecharm/layer.yaml @@ -0,0 +1 @@ +includes: ['layer:basic'] # if you use any interfaces, add them here diff --git a/tests/spread/smoketests/reactive-resolute/reactivecharm/metadata.yaml b/tests/spread/smoketests/reactive-resolute/reactivecharm/metadata.yaml new file mode 100644 index 000000000..71e39d76f --- /dev/null +++ b/tests/spread/smoketests/reactive-resolute/reactivecharm/metadata.yaml @@ -0,0 +1,12 @@ +name: reactive-test +summary: Testing reactive charms +maintainer: root +description: | + This is + indeed + a multi-line description +tags: + # Replace "misc" with one or more whitelisted tags from this list: + # https://jujucharms.com/docs/stable/authors-charm-metadata + - misc +subordinate: false diff --git a/tests/spread/smoketests/reactive-resolute/reactivecharm/reactive/reactivecharm.py b/tests/spread/smoketests/reactive-resolute/reactivecharm/reactive/reactivecharm.py new file mode 100644 index 000000000..89e4fff6c --- /dev/null +++ b/tests/spread/smoketests/reactive-resolute/reactivecharm/reactive/reactivecharm.py @@ -0,0 +1,17 @@ +from charms.reactive import set_flag, when_not + + +@when_not("reactivecharm.installed") +def install_reactivecharm(): + # Do your setup here. + # + # If your charm has other dependencies before it can install, + # add those as @when() clauses above, or as additional @when() + # decorated handlers below. + # + # See the following for information about reactive charms: + # + # * https://jujucharms.com/docs/devel/developer-getting-started + # * https://github.com/juju-solutions/layer-basic#overview + # + set_flag("reactivecharm.installed") diff --git a/tests/spread/smoketests/reactive-resolute/task.yaml b/tests/spread/smoketests/reactive-resolute/task.yaml new file mode 100644 index 000000000..e0bc2fc18 --- /dev/null +++ b/tests/spread/smoketests/reactive-resolute/task.yaml @@ -0,0 +1,30 @@ +summary: pack a simple reactive charm on 26.04 +priority: 100 + +systems: + - ubuntu-26.04-64 + +environment: + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1" + +prepare: | + cd reactivecharm + + git init + git config --global --add safe.directory $PWD + git add . + git config user.email "you@example.com" + git config user.name "Your Name" + git commit -m 'initial commit' + +restore: | + pushd reactivecharm + charmcraft clean + popd + + rm -rf reactivecharm + +execute: | + cd reactivecharm + charmcraft pack + test -f reactive-test*.charm diff --git a/tests/spread/smoketests/reactive/task.yaml b/tests/spread/smoketests/reactive/task.yaml deleted file mode 100644 index 9d8628a58..000000000 --- a/tests/spread/smoketests/reactive/task.yaml +++ /dev/null @@ -1,56 +0,0 @@ -summary: pack a simple reactive charm -# Run these early since they take a while. -priority: 100 - -environment: - CHARM_SNAP_CHANNEL/stable: stable - CHARM_SNAP_CHANNEL/two: 2.x/stable - CHARM_SNAP_CHANNEL/resolute: latest/candidate - BASE_CHANNEL/stable,two: "22.04" - BASE_CHANNEL/resolute: "26.04" - -prepare: | - cd reactivecharm - - if [ "$BASE_CHANNEL" = "26.04" ]; then - cat > charmcraft.yaml < charmcraft.yaml # only to configure to use staging - type: bundle - charmhub: - api-url: https://api.staging.charmhub.io - storage-url: https://storage.staging.snapcraftcontent.com - EOF - -restore: | - rm -rf charm - execute: | - cd charm - charmcraft register $CHARM_NAME package_type=$(charmcraft names --format=json | jq -r --arg charm_name $CHARM_NAME '.[] | select(.name==$charm_name) | .type') echo $package_type diff --git a/tests/spread/store/resources/task.yaml b/tests/spread/store/resources/task.yaml index b55af71bf..9c473b6ee 100644 --- a/tests/spread/store/resources/task.yaml +++ b/tests/spread/store/resources/task.yaml @@ -54,6 +54,9 @@ execute: | cd charm start_datetime=$(date -u --iso-8601=seconds) + # local image hash may be different from registry + image_id=$(docker inspect --format='{{.Id}}' hello-world@sha256:18a657d0cc1c7d0678a3fbea8b7eb4918bba25968d3e1b0adebfa71caddbc346) + # pack and upload charmcraft pack --verbose last_charm_revno=$(charmcraft upload $(ls $CHARM_DEFAULT_NAME*.charm) --format=json | jq .revision) @@ -81,8 +84,7 @@ execute: | [[ "$new_architectures" == '["all"]' ]] # upload and verify the OCI image resource by digest - # This is a different image hash because it's what Docker contains locally on amd64. - uploaded_revision=$(charmcraft upload-resource --format=json $CHARM_DEFAULT_NAME example-image --image=sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 | jq -r '.revision') + uploaded_revision=$(charmcraft upload-resource --format=json $CHARM_DEFAULT_NAME example-image --image="$image_id" | jq -r '.revision') sleep 3 # Give the store a bit of time to process it last_revision=$(charmcraft resource-revisions $CHARM_DEFAULT_NAME example-image --format=json | jq -r 'max_by(.revision)') last_image_revno=$(echo $last_revision | jq .revision) @@ -90,7 +92,7 @@ execute: | last_revision_created=$(echo $last_revision | jq -r '.["created at"]') # Upload and verify the OCI image resource by image ID. - uploaded_revision=$(charmcraft upload-resource --format=json $CHARM_DEFAULT_NAME example-image --image=feb5d9fea6a5 | jq -r '.revision') + uploaded_revision=$(charmcraft upload-resource --format=json $CHARM_DEFAULT_NAME example-image --image="${image_id:7:12}" | jq -r '.revision') sleep 3 # Give the store a bit of time to process it last_revision=$(charmcraft resource-revisions $CHARM_DEFAULT_NAME example-image --format=json | jq -r 'max_by(.revision)') last_image_revno=$(echo $last_revision | jq .revision) diff --git a/tests/spread/store/ubuntu-sso-login/task.yaml b/tests/spread/store/ubuntu-sso-login/task.yaml new file mode 100644 index 000000000..ee88f24c2 --- /dev/null +++ b/tests/spread/store/ubuntu-sso-login/task.yaml @@ -0,0 +1,15 @@ +summary: validate the Ubuntu One SSO export login flow + +execute: | + expect << 'EOF' + spawn charmcraft login --export=/tmp/sso-creds.login + expect "Email address: " + send "$env(STAGING_SSO_USERNAME)\r" + expect "Password: " + send "$env(STAGING_SSO_PASSWORD)\r" + expect eof + EOF + CHARMCRAFT_AUTH=$(cat /tmp/sso-creds.login) charmcraft whoami + +restore: | + rm -f /tmp/sso-creds.login diff --git a/tests/spread/tools.sh b/tests/spread/tools.sh new file mode 100644 index 000000000..1445f51de --- /dev/null +++ b/tests/spread/tools.sh @@ -0,0 +1,17 @@ +tools.setup_snapd_proxy() { + if [ "${SNAPD_USE_PROXY:-}" != true ]; then + return + fi + + local SNAPD_CONFD="/etc/systemd/system/snapd.service.d" + mkdir -p "$SNAPD_CONFD" + + cat < ${SNAPD_CONFD}/proxy.conf +[Service] +Environment=HTTPS_PROXY="$HTTPS_PROXY" HTTP_PROXY="$HTTP_PROXY" https_proxy="$HTTPS_PROXY" http_proxy="$HTTP_PROXY" NO_PROXY="$NO_PROXY" no_proxy="$NO_PROXY" +EOF + + # Since the service config changed, restart + systemctl daemon-reload + systemctl restart snapd.service +} diff --git a/tests/spread/ubuntu-20.04/charm/bases/requirements.txt b/tests/spread/ubuntu-20.04/charm/bases/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-20.04/charm/bases/requirements.txt +++ b/tests/spread/ubuntu-20.04/charm/bases/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/ubuntu-20.04/charm/platforms/requirements.txt b/tests/spread/ubuntu-20.04/charm/platforms/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-20.04/charm/platforms/requirements.txt +++ b/tests/spread/ubuntu-20.04/charm/platforms/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/ubuntu-22.04/charm/bases/requirements.txt b/tests/spread/ubuntu-22.04/charm/bases/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-22.04/charm/bases/requirements.txt +++ b/tests/spread/ubuntu-22.04/charm/bases/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/ubuntu-22.04/charm/platforms/requirements.txt b/tests/spread/ubuntu-22.04/charm/platforms/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-22.04/charm/platforms/requirements.txt +++ b/tests/spread/ubuntu-22.04/charm/platforms/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/ubuntu-24.04/charm/platforms/requirements.txt b/tests/spread/ubuntu-24.04/charm/platforms/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-24.04/charm/platforms/requirements.txt +++ b/tests/spread/ubuntu-24.04/charm/platforms/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/ubuntu-25.10/charm/platforms/requirements.txt b/tests/spread/ubuntu-25.10/charm/platforms/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-25.10/charm/platforms/requirements.txt +++ b/tests/spread/ubuntu-25.10/charm/platforms/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/spread/ubuntu-26.04/charm/platforms/requirements.txt b/tests/spread/ubuntu-26.04/charm/platforms/requirements.txt index e6e06ee8e..ad81fb7b0 100644 --- a/tests/spread/ubuntu-26.04/charm/platforms/requirements.txt +++ b/tests/spread/ubuntu-26.04/charm/platforms/requirements.txt @@ -1,3 +1,3 @@ ops==2.5.0 -pyyaml==6.0.2 +pyyaml==6.0.3 websocket-client==1.8.0 diff --git a/tests/test_env.py b/tests/test_env.py index d964a2e32..7e99ec2f4 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -79,3 +79,15 @@ def test_is_charmcraft_running_in_managed_mode(monkeypatch, managed, result): monkeypatch.setenv(const.MANAGED_MODE_ENV_VAR, managed) assert env.is_charmcraft_running_in_managed_mode() == result + + +def test_get_store_config_sso_url_default(monkeypatch): + monkeypatch.delenv(const.STORE_SSO_URL_ENV_VAR, raising=False) + config = env.get_store_config() + assert config.login_url == "https://login.ubuntu.com" + + +def test_get_store_config_sso_url(monkeypatch): + monkeypatch.setenv(const.STORE_SSO_URL_ENV_VAR, "https://login.staging.ubuntu.com") + config = env.get_store_config() + assert config.login_url == "https://login.staging.ubuntu.com" diff --git a/tests/unit/commands/test_lifecycle.py b/tests/unit/commands/test_lifecycle.py index 5b15606a3..a211fac7e 100644 --- a/tests/unit/commands/test_lifecycle.py +++ b/tests/unit/commands/test_lifecycle.py @@ -89,7 +89,7 @@ def test_pack_update_charm_libs_empty( libs_service = cast("CharmLibsService", service_factory.get("charm_libs")) mock_write = mock.Mock(wraps=libs_service.write) - libs_service.write = mock_write # ty: ignore[invalid-assignment] + libs_service.write = mock_write pack._update_charm_libs() @@ -273,3 +273,48 @@ def test_move_artifacts_creates_output_dir( pack._run(parsed_args) assert (output_dir / artifact_name).read_text() == "charm content" + + def test_no_move_when_project_dir_equals_output_dir( + self, + fake_project_dir: pathlib.Path, + pack: lifecycle.PackCommand, + service_factory: services.ServiceFactory, + ): + """Regression test for https://github.com/canonical/charmcraft/issues/2361.""" + project_dir = fake_project_dir + output_dir = fake_project_dir # Same as project_dir + + artifact_name = "my-charm_ubuntu-22.04-amd64.charm" + # The artifact exists in the project dir (as it would after a real managed build). + (project_dir / artifact_name).write_text("charm content") + + state_service = service_factory.get("state") + state_service.set("artifact", "test-platform", value=artifact_name) + + parsed_args = argparse.Namespace( + output=output_dir, + project_dir=project_dir, + destructive_mode=True, + shell=False, + shell_after=False, + debug=False, + platform=None, + build_for=None, + ) + project = cast("CharmcraftProject", service_factory.get("project").get()) + project.charm_libs = [] + + with ( + mock.patch( + "charmcraft.application.commands.lifecycle.is_managed_mode", + return_value=False, + ), + mock.patch.object( + lifecycle.PackCommand.__mro__[1], # craft-application PackCommand + "_run", + ), + ): + pack._run(parsed_args) + + # Artifact should still be in project_dir (no move since dirs are the same) + assert (project_dir / artifact_name).read_text() == "charm content" diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py new file mode 100644 index 000000000..0690f0425 --- /dev/null +++ b/tests/unit/commands/test_remote.py @@ -0,0 +1,47 @@ +# Copyright 2026 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For further info, check https://github.com/canonical/charmcraft + +"""Unit tests for remote-build""" + +import argparse + +from pytest_mock import MockerFixture + +from charmcraft import services +from charmcraft.application import APP_METADATA +from charmcraft.application.commands.remote import RemoteBuild +from charmcraft.services.project import ProjectService + + +def test_remote_build_project_name_attr_regression( + mocker: MockerFixture, service_factory: services.ServiceFactory +) -> None: + """Regression test for https://github.com/canonical/charmcraft/issues/2598 + The project service was being erroneously cast as the project model, causing + a later access to the `.name` field not be caught by linters. + """ + remote_build_cmd = RemoteBuild({"app": APP_METADATA, "services": service_factory}) + namespace = argparse.Namespace( + launchpad_accept_public_upload=True, + recover=False, + launchpad_timeout=0, + ) + mocker.patch("charmcraft.services.remotebuild.RemoteBuildService") + project_spy = mocker.spy(ProjectService, "get") + + remote_build_cmd.run(namespace) + + project_spy.assert_called_once() diff --git a/tests/unit/commands/test_store.py b/tests/unit/commands/test_store.py index 36135f9cc..4949826e9 100644 --- a/tests/unit/commands/test_store.py +++ b/tests/unit/commands/test_store.py @@ -24,10 +24,10 @@ import craft_application import craft_cli.pytest_plugin -import craft_store import pytest from craft_cli import CraftError from craft_store import models, publisher +from craft_store.errors import UbuntuOneOtpRequiredError from craft_store.publisher import Releases from charmcraft import errors, store @@ -35,7 +35,10 @@ from charmcraft.application.commands import SetResourceArchitecturesCommand from charmcraft.application.commands import store as store_commands from charmcraft.application.commands.store import ( + CHARMLIBS_DEPRECATION_WARNING, + CreateLibCommand, FetchLibs, + ListLibCommand, LoginCommand, PublishLibCommand, ) @@ -50,9 +53,16 @@ """ -def test_login_basic_no_export(service_factory, mock_store_client): - cmd = LoginCommand({"app": APP_METADATA, "services": service_factory}) +def test_login_basic_no_export(mocker, monkeypatch, service_factory, mock_store_client): + monkeypatch.setattr( + "craft_cli.messages.Emitter.prompt", + lambda self, *a, **kw: ( + "test@example.com" if not kw.get("hide") else "test-password" + ), + ) + mocker.patch("charmcraft.services.store.UbuntuOneLogin.login_with") + cmd = LoginCommand({"app": APP_METADATA, "services": service_factory}) cmd.run( argparse.Namespace( charm=None, @@ -71,20 +81,35 @@ def test_login_basic_no_export(service_factory, mock_store_client): @pytest.mark.parametrize("permission", [None, [], ["package-manage"]]) @pytest.mark.parametrize("ttl", [None, 0, 2**65]) def test_login_export( + mocker, monkeypatch, service_factory, mock_store_client, + fake_path, charm, bundle, channel, permission, ttl, ): - mock_client_cls = mock.Mock(return_value=mock_store_client) - monkeypatch.setattr(craft_store, "StoreClient", mock_client_cls) - mock_store_client.login.return_value = "Some store credentials" - cmd = LoginCommand({"app": APP_METADATA, "services": service_factory}) + monkeypatch.setattr( + "craft_cli.messages.Emitter.prompt", + lambda self, *a, **kw: ( + "test@example.com" if not kw.get("hide") else "test-password" + ), + ) + + export_file = fake_path / "charmhub.login" + fake_encoded = "base64encodedcreds==" + + mock_auth = mock.Mock() + mock_auth.get_credentials.return_value = "store-token" + mock_auth.encode_credentials.return_value = fake_encoded + mocker.patch("charmcraft.services.store.UbuntuOneLogin.login_with") + mocker.patch("charmcraft.services.store.craft_store.Auth", return_value=mock_auth) + mocker.patch("charmcraft.services.store.craft_store.UbuntuOneAuth") + cmd = LoginCommand({"app": APP_METADATA, "services": service_factory}) cmd.run( argparse.Namespace( charm=charm, @@ -92,12 +117,48 @@ def test_login_export( channel=channel, permission=permission, ttl=ttl, - export=pathlib.Path("charmhub.login"), + export=export_file, + ) + ) + + assert export_file.read_text() == fake_encoded + + +def test_login_export_otp( + mocker, monkeypatch, service_factory, mock_store_client, fake_path +): + prompts = iter(["test@example.com", "test-password", "123456"]) + monkeypatch.setattr( + "craft_cli.messages.Emitter.prompt", + lambda self, *a, **kw: next(prompts), + ) + + export_file = fake_path / "charmhub.login" + fake_encoded = "base64encodedcreds==" + + mock_auth = mock.Mock() + mock_auth.get_credentials.return_value = "store-token" + mock_auth.encode_credentials.return_value = fake_encoded + + mocker.patch( + "charmcraft.services.store.UbuntuOneLogin.login_with", + side_effect=[UbuntuOneOtpRequiredError(), None], + ) + mocker.patch("charmcraft.services.store.craft_store.Auth", return_value=mock_auth) + mocker.patch("charmcraft.services.store.craft_store.UbuntuOneAuth") + cmd = LoginCommand({"app": APP_METADATA, "services": service_factory}) + cmd.run( + argparse.Namespace( + charm=None, + bundle=None, + channel=None, + permission=None, + ttl=None, + export=export_file, ) ) - assert pathlib.Path("charmhub.login").read_text() == "Some store credentials" - mock_store_client.login.assert_called_once() + assert export_file.read_text() == fake_encoded @pytest.mark.parametrize( @@ -162,7 +223,7 @@ def test_publish_lib_error(monkeypatch, new_path: pathlib.Path) -> None: ) -def test_publish_lib_same_is_noop(monkeypatch, new_path: pathlib.Path) -> None: +def test_publish_lib_same_is_noop(monkeypatch, new_path: pathlib.Path, emitter) -> None: # Publishing the same version of a library with the same hash should not result # in an error return. mock_service_factory = mock.Mock(spec=craft_application.ServiceFactory) @@ -197,6 +258,44 @@ def test_publish_lib_same_is_noop(monkeypatch, new_path: pathlib.Path) -> None: == 0 ) + emitter.assert_progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) + + +def test_create_lib_warns_deprecation( + monkeypatch, new_path: pathlib.Path, emitter, service_factory +) -> None: + mock_store = mock.Mock() + mock_store.return_value.create_library_id.return_value = "lib-id" + monkeypatch.setattr(store_commands, "Store", mock_store) + mock_template = mock.Mock() + mock_template.render.return_value = "LIBAPI = 0\nLIBID = 'lib-id'\nLIBPATCH = 1\n" + mock_environment = mock.Mock() + mock_environment.get_template.return_value = mock_template + monkeypatch.setattr( + store_commands.utils, + "get_templates_environment", + mock.Mock(return_value=mock_environment), + ) + + cmd = CreateLibCommand({"app": APP_METADATA, "services": service_factory}) + + cmd.run(argparse.Namespace(name="my_lib", format=False)) + + emitter.assert_progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) + + +def test_list_lib_warns_deprecation(monkeypatch, emitter, service_factory) -> None: + mock_store = mock.Mock() + mock_store.return_value.get_libraries_tips.return_value = {} + monkeypatch.setattr(store_commands, "Store", mock_store) + + cmd = ListLibCommand({"app": APP_METADATA, "services": service_factory}) + + cmd.run(argparse.Namespace(name="test-charm", format=False)) + + emitter.assert_progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) + emitter.assert_message("No libraries found for charm test-charm.") + @pytest.mark.parametrize( ("updates", "expected"), @@ -233,6 +332,7 @@ def test_fetch_libs_no_charm_libs( with pytest.raises(errors.LibraryError) as exc_info: fetch_libs.run(argparse.Namespace()) + emitter.assert_progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) assert exc_info.value.resolution == "Add a 'charm-libs' section to charmcraft.yaml." @@ -366,6 +466,7 @@ def test_fetch_libs_success( fetch_libs.run(argparse.Namespace()) + emitter.assert_progress(CHARMLIBS_DEPRECATION_WARNING, permanent=True) emitter.assert_progress("Getting library metadata from charmhub") emitter.assert_message("Downloaded 1 charm libraries.") diff --git a/tests/unit/models/test_project.py b/tests/unit/models/test_project.py index ad1b432e9..74e040571 100644 --- a/tests/unit/models/test_project.py +++ b/tests/unit/models/test_project.py @@ -740,7 +740,8 @@ def test_resolute_base_does_not_need_build_base(): ) -def test_resolute_base_supports_reactive_plugin(): +def test_resolute_base_supports_reactive_plugin(monkeypatch): + monkeypatch.setenv(const.EXPERIMENTAL_EXTENSIONS_ENV_VAR, "1") project.PlatformCharm.unmarshal( { "type": "charm", @@ -754,6 +755,22 @@ def test_resolute_base_supports_reactive_plugin(): ) +def test_resolute_base_supports_charm_plugin(monkeypatch): + monkeypatch.setenv(const.EXPERIMENTAL_EXTENSIONS_ENV_VAR, "1") + project.PlatformCharm.unmarshal( + { + "type": "charm", + "name": "test-charm", + "summary": "", + "description": "", + "base": "ubuntu@26.04", + "platforms": {"amd64": None}, + "parts": {"charm": {"plugin": "charm"}}, + } + ) + + +@pytest.mark.xfail(strict=True, reason="craft-application#1092") def test_resolute_base_rejects_charm_plugin(): with pytest.raises( pydantic.ValidationError, @@ -772,10 +789,11 @@ def test_resolute_base_rejects_charm_plugin(): ) -def test_legacy_plugins_are_checked_against_build_base(): +@pytest.mark.xfail(strict=True, reason="craft-application#1092") +def test_charm_plugin_is_checked_against_build_base(): with pytest.raises( pydantic.ValidationError, - match="Cannot use 'charm' or 'reactive' plugins with base 'ubuntu@25.10'", + match="Cannot use 'charm' plugin with base 'ubuntu@26.04'", ): project.PlatformCharm.unmarshal( { @@ -784,14 +802,15 @@ def test_legacy_plugins_are_checked_against_build_base(): "summary": "", "description": "", "base": "ubuntu@24.04", - "build-base": "ubuntu@25.10", + "build-base": "ubuntu@26.04", "platforms": {"amd64": None}, - "parts": {"charm": {"plugin": "reactive"}}, + "parts": {"charm": {"plugin": "charm"}}, } ) -def test_charm_plugin_is_checked_against_build_base(): +@pytest.mark.xfail(strict=True, reason="craft-application#1092") +def test_resolute_base_rejects_charm_plugin_without_env_var(): with pytest.raises( pydantic.ValidationError, match="Cannot use 'charm' plugin with base 'ubuntu@26.04'", @@ -802,14 +821,32 @@ def test_charm_plugin_is_checked_against_build_base(): "name": "test-charm", "summary": "", "description": "", - "base": "ubuntu@24.04", - "build-base": "ubuntu@26.04", + "base": "ubuntu@26.04", "platforms": {"amd64": None}, "parts": {"charm": {"plugin": "charm"}}, } ) +def test_legacy_plugins_are_checked_against_build_base(): + with pytest.raises( + pydantic.ValidationError, + match="Cannot use 'charm' or 'reactive' plugins with base 'ubuntu@25.10'", + ): + project.PlatformCharm.unmarshal( + { + "type": "charm", + "name": "test-charm", + "summary": "", + "description": "", + "base": "ubuntu@24.04", + "build-base": "ubuntu@25.10", + "platforms": {"amd64": None}, + "parts": {"charm": {"plugin": "reactive"}}, + } + ) + + @pytest.mark.parametrize( "filename", [f.name for f in (pathlib.Path(__file__).parent / "valid_charms_yaml").iterdir()], diff --git a/tests/unit/services/test_store.py b/tests/unit/services/test_store.py index 2009d4977..fc5c2d86a 100644 --- a/tests/unit/services/test_store.py +++ b/tests/unit/services/test_store.py @@ -41,6 +41,8 @@ def store(service_factory, mock_store_anonymous_client) -> StoreService: store = StoreService(app=application.APP_METADATA, services=service_factory) store.client = mock.Mock(spec_set=client.Client) + store._auth = mock.Mock(spec=craft_store.Auth) + store._publisher = mock.Mock(spec_set=craft_store.PublisherGateway) store.anonymous_client = mock_store_anonymous_client return store @@ -48,7 +50,7 @@ def store(service_factory, mock_store_anonymous_client) -> StoreService: @pytest.fixture(scope="module") def reusable_store(): store = StoreService(app=application.APP_METADATA, services=None) # ty: ignore[invalid-argument-type] - store.client = mock.Mock(spec_set=craft_store.StoreClient) + store._auth = mock.Mock(spec=craft_store.Auth) store._publisher = mock.Mock(spec_set=craft_store.PublisherGateway) return store @@ -102,14 +104,21 @@ def test_ua_system_info_linux( ) -def test_setup_with_error(emitter: RecordingEmitter, store): - store.ClientClass = mock.Mock( - side_effect=[craft_store.errors.NoKeyringError, "I am a store!"] - ) +def test_setup_with_error(mocker, emitter: RecordingEmitter, store): + call_count = 0 + + def fake_setup_auth(*, ephemeral=False): + nonlocal call_count + call_count += 1 + if call_count == 1: + raise craft_store.errors.NoKeyringError + store._auth = mock.Mock(spec=craft_store.Auth) + mocker.patch.object(store, "_setup_auth", side_effect=fake_setup_auth) + mocker.patch.object(type(store), "_publisher", create=True) store.setup() - assert store.client == "I am a store!" + assert call_count == 2 emitter.assert_progress( "WARNING: Cannot get a keyring. Every store interaction that requires " "authentication will require you to log in again.", @@ -123,52 +132,46 @@ def test_get_description_default(monkeypatch, store): assert store._get_description() == "charmcraft@my-hostname" -@given(text=strategies.text()) -def test_get_description_override(reusable_store, text): - assert reusable_store._get_description(text) == text - - @given( permissions=strategies.lists(strategies.text()), - description=strategies.text(), ttl=strategies.integers(min_value=1), channels=strategies.lists(strategies.text()), ) -def test_login(reusable_store, permissions, description, ttl, channels): - client = cast(mock.Mock, reusable_store.client) - client.reset_mock(return_value=True, side_effect=True) - - reusable_store.login( - permissions=permissions, description=description, ttl=ttl, channels=channels - ) - - client.login.assert_called_once_with( - permissions=permissions, - description=description, - ttl=ttl, - packages=None, - channels=channels, - ) +def test_login(reusable_store, permissions, ttl, channels): + with mock.patch( + "charmcraft.services.store.UbuntuOneLogin.login_with" + ) as mock_login: + reusable_store.login( + email="user@example.com", + password="pw123", + permissions=permissions, + ttl=ttl, + channels=channels, + ) + + mock_login.assert_called_once() + kwargs = mock_login.call_args[1] + assert kwargs["email"] == "user@example.com" + assert kwargs["password"] == "pw123" + assert kwargs["permissions"] == permissions + assert kwargs["ttl"] == ttl + assert kwargs["channels"] == channels def test_login_failure(store): - client = cast(mock.Mock, store.client) - client.login.side_effect = craft_store.errors.CredentialsAlreadyAvailable( - "charmcraft", "host" + store._auth.ensure_no_credentials.side_effect = ( + craft_store.errors.CredentialsAlreadyAvailable("charmcraft", "host") ) - with pytest.raises( errors.CraftError, match="Cannot login because credentials were found" ): - store.login() + store.login(email="user@example.com", password="pw123") def test_logout(store): - client = cast(mock.Mock, store.client) - store.logout() - client.logout.assert_called_once_with() + store._auth.del_credentials.assert_called_once_with() def test_create_tracks(reusable_store: StoreService): @@ -290,29 +293,45 @@ def test_set_resource_revisions_architectures_response_form( assert actual == expected -def test_get_credentials(monkeypatch, store): - mock_client_class = mock.Mock(spec_set=craft_store.StoreClient) - mock_client = mock_client_class.return_value - monkeypatch.setattr(craft_store, "StoreClient", mock_client_class) +def test_get_credentials(mocker, store): + """get_credentials() uses UbuntuOneLogin with ephemeral auth and exchanges macaroons.""" + mock_store_token = "exchanged-store-token" + mock_encoded = "base64encodedcreds" - store.get_credentials() + mock_login = mocker.patch("charmcraft.services.store.UbuntuOneLogin.login_with") + mock_auth_cls = mocker.patch("charmcraft.services.store.craft_store.Auth") + mock_auth_instance = mock.Mock() + mock_auth_instance.get_credentials.return_value = mock_store_token + mock_auth_instance.encode_credentials.return_value = mock_encoded + mock_auth_cls.return_value = mock_auth_instance - mock_client_class.assert_called_once_with( - application_name="charmcraft", - base_url=mock.ANY, - storage_base_url=mock.ANY, - endpoints=mock.ANY, - environment_auth=None, - user_agent=store._user_agent, - ephemeral=True, + mock_u1_auth_cls = mocker.patch( + "charmcraft.services.store.craft_store.UbuntuOneAuth" ) - mock_client.login.assert_called_once_with( - permissions=mock.ANY, - description=store._get_description(), - ttl=mock.ANY, - packages=None, - channels=None, + mock_u1_auth_instance = mock.Mock() + mock_u1_auth_cls.return_value = mock_u1_auth_instance + + result = store.get_credentials(email="user@example.com", password="pw123") + + mock_login.assert_called_once() + login_kwargs = mock_login.call_args[1] + assert login_kwargs["email"] == "user@example.com" + assert login_kwargs["password"] == "pw123" + assert login_kwargs["store_auth"] is mock_auth_instance + + mock_auth_cls.assert_called_once() + auth_kwargs = mock_auth_cls.call_args[1] + assert auth_kwargs.get("ephemeral") is True + + # Verify the exchange is triggered so the exported credential is a store token + mock_u1_auth_cls.assert_called_once_with( + auth=mock_auth_instance, + api_base_url=store._base_url, + client_description=store._get_description(), ) + mock_u1_auth_instance.get_token_from_keyring.assert_called_once() + + assert result == mock_encoded @given(name=strategies.text()) diff --git a/tests/unit/utils/test_store.py b/tests/unit/utils/test_store.py index 09b59bf67..b04132951 100644 --- a/tests/unit/utils/test_store.py +++ b/tests/unit/utils/test_store.py @@ -26,5 +26,5 @@ ) def test_get_packages(charms, bundles): packages = utils.get_packages(charms=charms, bundles=bundles) - result_names = [package.package_name for package in packages] + result_names = [package["name"] for package in packages] assert result_names == charms + bundles diff --git a/uv.lock b/uv.lock index 6d4ffc57d..ed7cbdc72 100644 --- a/uv.lock +++ b/uv.lock @@ -4,8 +4,7 @@ requires-python = ">=3.10" resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] @@ -89,16 +88,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.14.0" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, ] [[package]] @@ -257,7 +256,7 @@ wheels = [ [[package]] name = "build" -version = "1.5.0" +version = "1.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "os_name == 'nt' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, @@ -266,9 +265,9 @@ dependencies = [ { name = "pyproject-hooks" }, { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/21/6ec54248b4d0d51f12f3ca4aa77a128077d747a5db86cb5a2fcd9aedecbd/build-1.5.1.tar.gz", hash = "sha256:94e17f1db803ab22f46049376c44c8437c52090f0dfdf1adc43df56542d644fb", size = 112439, upload-time = "2026-07-09T06:21:59.632Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f7/2c3f99ff9282f1c1ec9f6298f2c03034658a0901eeacb3b3501cb488574c/build-1.5.1-py3-none-any.whl", hash = "sha256:f1a58fe2e5af5b0238a07b9e70207492c79ddebbdb1ad954fc86d62a56be3e0d", size = 31195, upload-time = "2026-07-09T06:21:58.551Z" }, ] [[package]] @@ -306,93 +305,121 @@ wheels = [ [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] @@ -526,13 +553,13 @@ types = [ [package.metadata] requires-dist = [ - { name = "craft-application", specifier = "~=7.0" }, + { name = "craft-application", specifier = "~=7.1" }, { name = "craft-cli", specifier = "~=3.4" }, - { name = "craft-grammar", specifier = ">=2.0.0" }, - { name = "craft-parts", specifier = "~=2.20" }, - { name = "craft-platforms", specifier = "~=0.10" }, - { name = "craft-providers", specifier = ">=3.4.0,<4.0" }, - { name = "craft-store", specifier = "~=3.2" }, + { name = "craft-grammar", specifier = ">=2.3.0" }, + { name = "craft-parts", specifier = "~=2.35" }, + { name = "craft-platforms", specifier = "~=0.11" }, + { name = "craft-providers", specifier = ">=3.7.1,<4.0" }, + { name = "craft-store", specifier = "~=3.4" }, { name = "distro", specifier = ">=1.7.0" }, { name = "docker", specifier = ">=7.0.0" }, { name = "humanize", specifier = ">=2.6.0" }, @@ -636,107 +663,89 @@ types = [ [[package]] name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, - { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, - { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, - { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, - { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, - { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, - { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, - { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, - { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, - { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, - { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, - { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, ] [[package]] @@ -754,14 +763,14 @@ wheels = [ [[package]] name = "click" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, ] [[package]] @@ -789,115 +798,100 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/69/0d2ef01ff4b8fcecd4cba920d11e92fa4f96ae412441d3b56a90a258e69b/coverage-7.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf", size = 219722, upload-time = "2026-05-26T20:38:14.002Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ae/9afdeaa31b9d9ce98124b6abf8bb49119bf71aecae04f8567c189d91299f/coverage-7.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf", size = 220240, upload-time = "2026-05-26T20:38:17.424Z" }, - { url = "https://files.pythonhosted.org/packages/51/69/c998589871df7ea7dba865cc5ee32b5a3e1d47ba6c68ef91104c7c46fa5e/coverage-7.14.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d", size = 246981, upload-time = "2026-05-26T20:38:19.266Z" }, - { url = "https://files.pythonhosted.org/packages/fc/10/1c7d04c13040dac531d21b712bbe08f902e6dd9b58f5d77875c4d030f8f2/coverage-7.14.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2", size = 248812, upload-time = "2026-05-26T20:38:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/c1/65/2a38a4607ef27cadcfbcee034dba5830ae2569f90144a0f4c7dbf47d30b0/coverage-7.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47", size = 250675, upload-time = "2026-05-26T20:38:22.159Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a2/a446ed9752a4a59b79e0fb6cbb319f6facb2183045c0725462625e66f87e/coverage-7.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550", size = 252590, upload-time = "2026-05-26T20:38:23.63Z" }, - { url = "https://files.pythonhosted.org/packages/9e/fd/e81fbd7ba752365546e9842b1cbdaad3d6919d2a522c590aef16a281ec5e/coverage-7.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e", size = 247691, upload-time = "2026-05-26T20:38:25.057Z" }, - { url = "https://files.pythonhosted.org/packages/53/35/f3c26fdaae9ea937d154ca4d372e5ea0a4167ff70d36c6074ac2eacb2f83/coverage-7.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f", size = 248716, upload-time = "2026-05-26T20:38:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/2e/14/940b6c49551fd343e8507ee2b0ba7af5d0aa04ed5bf768285cb7c72a9884/coverage-7.14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1", size = 246721, upload-time = "2026-05-26T20:38:28.282Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2c/40fc0634186c28292a662dff578866b3913983d6c375a3c2a74020938719/coverage-7.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5", size = 250533, upload-time = "2026-05-26T20:38:29.753Z" }, - { url = "https://files.pythonhosted.org/packages/de/e3/2c26bf1e811f9df991ff2a9bdddebdd13ee0665d564df7d05979f9146297/coverage-7.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b", size = 246990, upload-time = "2026-05-26T20:38:31.516Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b0/060260ef56bd92363ebdce0c7095ce422b06e69aae71828efeca473ab1ca/coverage-7.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332", size = 247593, upload-time = "2026-05-26T20:38:33.065Z" }, - { url = "https://files.pythonhosted.org/packages/63/f3/501502046efeb0d6d94b5ca54941d95f1184183dd6bdb7f283985783bb4a/coverage-7.14.1-cp310-cp310-win32.whl", hash = "sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59", size = 222330, upload-time = "2026-05-26T20:38:35.36Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5d/1bf99f2c558f128faf7906817ccbdb576ba815d3b41ce2ac1719b70a3663/coverage-7.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253", size = 223261, upload-time = "2026-05-26T20:38:37.196Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, - { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, - { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, - { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, - { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, - { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, - { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, - { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, - { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, - { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, - { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, - { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, - { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, - { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, - { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, - { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, - { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, - { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, - { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, - { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, - { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, - { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, - { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, - { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, - { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, - { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, - { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, - { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, - { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, - { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, +version = "7.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/97/c52dc440c390b6cfa87be9432b141a956e2d56d9b9f5fc8bd71c5f471722/coverage-7.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50913d4bf5ddafa6ca3693da5e4dd833dd1b772e0283c99ca7f7d287db67331a", size = 220539, upload-time = "2026-07-02T13:08:19.252Z" }, + { url = "https://files.pythonhosted.org/packages/3f/26/602de8c2aec7e2e3e99ebfb8e04ba65598f746275396eea5f6794ff4673f/coverage-7.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:359e141ccd33893ce3f1ad5525f8b96083003677c82182e5907d62d4ea5799fc", size = 221058, upload-time = "2026-07-02T13:08:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/fc/13/ebab0743138891c1d646d61e247ec29639afcbb6c4e1905e6a0f0c75291a/coverage-7.15.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3200b6204935f928c64b2ca1f923ab8c1acb7c9de45ec61569711b34d25cccaf", size = 247797, upload-time = "2026-07-02T13:08:22.474Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/b6ffb9e042aa48dc4144a8a65529affaec8dca0685309353614a2a7386ad/coverage-7.15.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:be616bf61346883b2cfdc5178669647e03531d81ab761a7e378558b7e8bcb628", size = 249626, upload-time = "2026-07-02T13:08:23.803Z" }, + { url = "https://files.pythonhosted.org/packages/9c/06/243ff05b652333d8e3d060c11223efc2723b19cacf6605e433fa686ab5d4/coverage-7.15.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7bafc3fe1059463a8fdd97ca79972d6e2bf819d775c7d54991b5b1971201d6", size = 251493, upload-time = "2026-07-02T13:08:25.397Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2b/867faa17030a806114dae388b32a3fa929d8cd4bf39226fbc11f6e6bb705/coverage-7.15.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b713aa7fcf325a01d4184d848acb46fd84f78fdb0978470c636b23a06a753d91", size = 253406, upload-time = "2026-07-02T13:08:26.842Z" }, + { url = "https://files.pythonhosted.org/packages/94/c0/d789ce18f6605afc4895db75723424be2ef494282f77f61d8e5832923183/coverage-7.15.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e38e6fba2d56652fdfaf0231f8f78aeb805234a912de25dc291ee5cce5b8faa4", size = 248512, upload-time = "2026-07-02T13:08:28.398Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/b2673c30739f4a2e06649a0a38ad8b093c4d865462dc7bab0e9524a2c3b1/coverage-7.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:884499f42e382675be80770391983b90e0c0c774d87dbeeebf5f991cf6612b20", size = 249532, upload-time = "2026-07-02T13:08:29.731Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/acd79e9a41beabee92b623afe4f30b549916f48566271475f2907e752828/coverage-7.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:840481b12e083dbcbafab14794a8781a958edf327c8d3d70b4eee42f9b8253aa", size = 247537, upload-time = "2026-07-02T13:08:31.173Z" }, + { url = "https://files.pythonhosted.org/packages/12/d4/2d301c4d1b3238d7c88b70ab9d13fd53ed9505662a7ff1b46ba1e2e4e3c3/coverage-7.15.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:276646e9481703d09f854f3b2f018f24e19fd7049ae670a92570043eb97203b1", size = 251348, upload-time = "2026-07-02T13:08:32.63Z" }, + { url = "https://files.pythonhosted.org/packages/35/bb/c67708b2bc00f32e12805ec23d5fa677a0a51652f449341a89f9d6b1b715/coverage-7.15.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4de4b4d3f5545aa6c60dc4efd9c63b5b5dcc3bf00fe83146b2bdfffb8f6613bd", size = 247806, upload-time = "2026-07-02T13:08:33.931Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6c/57c4f653c47a6e917748f8938e389e72fbcae44e3643cd906664f0477a13/coverage-7.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5c504097b2a89b1e85bc6070d920df77daec701337e3aeef2c17775a5dd0ca90", size = 248410, upload-time = "2026-07-02T13:08:35.189Z" }, + { url = "https://files.pythonhosted.org/packages/6c/94/bb083041aef828903668f134273f319f2bd49224962875359c52faa5497f/coverage-7.15.0-cp310-cp310-win32.whl", hash = "sha256:f6e80ed91f98316e86b9c137206b04b2bcfbffccbdff49bd2eb09dddb1cf14e0", size = 222588, upload-time = "2026-07-02T13:08:36.486Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/a09d8ee618956f626741b0734854bac4425a00e10c0565f5abca64e7e751/coverage-7.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3b3e22030f3f6f5e01a5ce69936552a5c0f6992b7698777377b99041961031f", size = 223214, upload-time = "2026-07-02T13:08:37.885Z" }, + { url = "https://files.pythonhosted.org/packages/ae/23/82e910835ef4b8391047025e1d53aa48d66029f444eb8b25373c849bf503/coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a", size = 220662, upload-time = "2026-07-02T13:08:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0d/c7b213dde2f1579de5231062b386d8413f79c11667eb58c39319b25991da/coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118", size = 221168, upload-time = "2026-07-02T13:08:40.471Z" }, + { url = "https://files.pythonhosted.org/packages/33/77/d000aeedfac085088337b3c7becdad328474b1f8a9e4c9368a0c99605d68/coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2", size = 251587, upload-time = "2026-07-02T13:08:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e0/86787c56b9df17afd370d5e293515dd4d9a107a561d13054873eefad8ecc/coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8", size = 253497, upload-time = "2026-07-02T13:08:43.387Z" }, + { url = "https://files.pythonhosted.org/packages/3f/02/181bc917359299c07dead6270f94e411151c8b60cec905c33499da69afe6/coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5", size = 255607, upload-time = "2026-07-02T13:08:44.897Z" }, + { url = "https://files.pythonhosted.org/packages/b9/35/ca5e7427699913da6788c4f910e73ab16c5f4b59ec5d3a999dce2a45112f/coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723", size = 257563, upload-time = "2026-07-02T13:08:46.334Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4d/b8220bacc2fc3c4e9078e27c32e99fb411479a4718a72bdd00036a9891c8/coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735", size = 251726, upload-time = "2026-07-02T13:08:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e4/2e145da1991d72189b9c3cf7eca05c716ee7080d099aaea6757cfc7df008/coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9", size = 253301, upload-time = "2026-07-02T13:08:49.5Z" }, + { url = "https://files.pythonhosted.org/packages/72/28/d2c841d698bf762e481f08bd4839d370246b6d9b61dab085a7b20b201a08/coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035", size = 251361, upload-time = "2026-07-02T13:08:51.304Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ed/55d9ffde994fba3897c0c783f77a7d053b0c18787f6892ed5b0aed73f469/coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671", size = 255129, upload-time = "2026-07-02T13:08:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c0/ecbf33b8c460ea2718aeb813e2df8140d0370e5f67261c31524ceb0a2a8d/coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695", size = 251081, upload-time = "2026-07-02T13:08:54.188Z" }, + { url = "https://files.pythonhosted.org/packages/a9/de/fb87b4261f54448dd2b9504ef19a58be42cef0d9520595fbfe1219b15234/coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540", size = 251988, upload-time = "2026-07-02T13:08:55.726Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/3494d5f291b9a4cb868f73c11221a8bd2d5bd761a8f9acea61ff57128dd1/coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4", size = 222754, upload-time = "2026-07-02T13:08:57.091Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ee/cd4847ebc9be6a9c0123d763645a6f1f3be6b8c58c962706368b79cbac07/coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6", size = 223225, upload-time = "2026-07-02T13:08:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/57/37/5011581aa7f2be498b97dcc7c9902192442a42f4f9a748aeadb3d6506b42/coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640", size = 222774, upload-time = "2026-07-02T13:09:00.074Z" }, + { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, + { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, + { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, + { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, + { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, + { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, + { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, + { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, + { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, + { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, + { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, + { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, + { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, + { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, + { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, + { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, ] [package.optional-dependencies] @@ -907,7 +901,7 @@ toml = [ [[package]] name = "craft-application" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -930,14 +924,14 @@ dependencies = [ { name = "snap-http" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/82/f364723d620fd5e96e2d1a2a4e0f975113f3be4650923cd028ae33f1c1a0/craft_application-7.0.0.tar.gz", hash = "sha256:f341e05992b8f64f1d86ec927e3bb66ec72abcecdbca02117a99fd3b4c093715", size = 656712, upload-time = "2026-06-02T21:33:43.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/c3/be96c01c1b92a7a7eb6cafa881ccd09a6b9e1db1355835fc98f001680cc3/craft_application-7.1.0.tar.gz", hash = "sha256:e0a3fb2080bf1dd6daf8b570262716f79a5f307a87d3ed2b986eaca50e07efed", size = 663202, upload-time = "2026-07-07T22:02:40.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/eb/37b78a3d15028840f94d260e973a7c6163b824b76faeaa95560080c0f850/craft_application-7.0.0-py3-none-any.whl", hash = "sha256:dc521efbf0bed63c0a68f84a79d818d31205a3fcd09090813e4a8c779ca71c3c", size = 206693, upload-time = "2026-06-02T21:33:41.952Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ce/169bdacdfbda0fe8a880d914be0794fc5379b7ec577546444f1572f4762d/craft_application-7.1.0-py3-none-any.whl", hash = "sha256:3319310644d1d935fd7e2fb285fa38bc87c490f3d4fb72fed934e6202164b7bc", size = 212571, upload-time = "2026-07-07T22:02:38.317Z" }, ] [[package]] name = "craft-archives" -version = "2.2.0" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distro" }, @@ -948,14 +942,14 @@ dependencies = [ { name = "pydantic" }, { name = "python-debian" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/4c/63ce5d6c91b4b269f862460392bd70fd0594f7902641b5d0bc82aa3f1c41/craft_archives-2.2.0.tar.gz", hash = "sha256:0bfd6dc3c2710605b97292df952032af24bf94ebf2f284f355d70f1bb2e3fe20", size = 190579, upload-time = "2025-08-13T19:58:19.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/3e/3ee557ac30aa1b9b6fa08de49fe2dd08727e329985073363f58098a45674/craft_archives-2.2.1.tar.gz", hash = "sha256:4f1858de9ed60e47284006cdcead233df92322b3b5eff15d7a67aebfe782874e", size = 232087, upload-time = "2026-07-08T14:19:20.626Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/ee/b846bfadd719eac15b7aeed84bc4168065e5a230464d63ade46adfd89824/craft_archives-2.2.0-py3-none-any.whl", hash = "sha256:c5b1d51b1e7400c55562dfbd62581d71a2c72c3c08db61e2d52dd3f42c46346b", size = 38144, upload-time = "2025-08-13T19:58:17.599Z" }, + { url = "https://files.pythonhosted.org/packages/39/db/b41a72d10768602638ce0b6bfc6fe45a5e119442d510e7d2c3b79ee48dc1/craft_archives-2.2.1-py3-none-any.whl", hash = "sha256:4933a300785dfced3ff4c59f618a66a7442e62f601db386d13de46f4a27a811c", size = 39645, upload-time = "2026-07-08T14:19:18.974Z" }, ] [[package]] name = "craft-cli" -version = "3.4.0" +version = "3.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -963,9 +957,9 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/f8/6e4414523405f439f99ad1fa8b8839bf2a6030e569c1dee89e3e97a8ac15/craft_cli-3.4.0.tar.gz", hash = "sha256:72e7494c66cb78ba08a80b20685dbaa739c7ef18b2fafbde3105958ab55ae7aa", size = 231221, upload-time = "2026-04-07T21:45:10.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/55e3a42aed3c8ee408dca292c7736080161d6bcfd6d21a8374b74be90f63/craft_cli-3.4.1.tar.gz", hash = "sha256:e4396695e89f15fbd06255a78b38a362006cd711266d926c2b6cd2a546af61a5", size = 231519, upload-time = "2026-06-17T13:07:15.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9f/48733917496a7ddcd503768be1e6dd18e13ed30714a6f63b32ae27d45bf2/craft_cli-3.4.0-py3-none-any.whl", hash = "sha256:ad5bf69e86979a51824bca595a4df5d6e9572346be9ffbdab58b078b9b1cb7cc", size = 47271, upload-time = "2026-04-07T21:45:08.506Z" }, + { url = "https://files.pythonhosted.org/packages/56/d4/22381efb16c8479ecd1fe73e59511a44033b5e78e40499a32a153e7eaef5/craft_cli-3.4.1-py3-none-any.whl", hash = "sha256:78702bb254a63dd8a30841fe9773f77676a3acace8b4c363f7dce135d354a107", size = 47241, upload-time = "2026-06-17T13:07:13.789Z" }, ] [[package]] @@ -983,7 +977,7 @@ wheels = [ [[package]] name = "craft-parts" -version = "2.34.0" +version = "2.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml" }, @@ -996,9 +990,9 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/dc/a024e00afcaac70a1741a342e4de7a9c715b230875779058b84c2f088099/craft_parts-2.34.0.tar.gz", hash = "sha256:644c1733d29fbabff0dbb088a18b877652b4099de07a9f90563ed1fb6e620ce7", size = 1013434, upload-time = "2026-06-11T12:04:01.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/c6/3cdccef7c6b5d268ec2ceac05aaa5707ed6a25af46df30fc105fa227f533/craft_parts-2.35.0.tar.gz", hash = "sha256:63a236a3a9518506498f7a8bc351145f587977649bab80b633813270b5011a5c", size = 1015073, upload-time = "2026-06-17T18:18:59.118Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/a9/f344547c792d9f8dd93e130ba5be5c5dd7d120f8196393c51fd023f1861a/craft_parts-2.34.0-py3-none-any.whl", hash = "sha256:bfba031d8974c1b43c1266c1eb00f29f9eaef524ea59c95df9a7729b48eb18d1", size = 555932, upload-time = "2026-06-11T12:03:59.481Z" }, + { url = "https://files.pythonhosted.org/packages/d2/13/d24637f18fd8881530a8166954267f69de78b85d7d355d5787f710fd4f6c/craft_parts-2.35.0-py3-none-any.whl", hash = "sha256:b8c66a038812da0f19bfcdb4785934625b50d8515e55ea8c1ae358911c278a78", size = 555989, upload-time = "2026-06-17T18:18:55.642Z" }, ] [[package]] @@ -1017,7 +1011,7 @@ wheels = [ [[package]] name = "craft-providers" -version = "3.6.0" +version = "3.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -1027,14 +1021,14 @@ dependencies = [ { name = "requests" }, { name = "requests-unixsocket2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/bc/db06baf74ff9538282eb265f99b0147f3b8b775da583a9c6aa88a9d59227/craft_providers-3.6.0.tar.gz", hash = "sha256:dfffebb4a9f09f763b293fe396f9f4f17d917e18167d98b61f76cffd55556250", size = 325499, upload-time = "2026-04-30T17:43:56.728Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/f5/81525f63af39d73be1fae45fc169eb036781d745a0220f95d9b7f6165549/craft_providers-3.7.1.tar.gz", hash = "sha256:473e6228720dda9f042eae8b9584a1f1c41d6fd987c0fd9db0d69609d3c5f283", size = 380268, upload-time = "2026-07-02T14:03:49.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/88/a8cebf86e8fda0c542e70c40374d3edc0bf4cf92ba55151e3a4f60695253/craft_providers-3.6.0-py3-none-any.whl", hash = "sha256:3b3ee87e535f1a29555012ba8d050ae6ca9afbc91fc2f273656d57052a307854", size = 121782, upload-time = "2026-04-30T17:43:54.567Z" }, + { url = "https://files.pythonhosted.org/packages/72/ba/c86bb1f2b356299f2971e613071bd30eef39719d372fb1e3f116b078d1ca/craft_providers-3.7.1-py3-none-any.whl", hash = "sha256:594efc39b84af865f4b3733b6658ec9e34ff6ff2bb98a1ed3785fe82d4afdc83", size = 120989, upload-time = "2026-07-02T14:03:47.659Z" }, ] [[package]] name = "craft-store" -version = "3.3.0" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1049,9 +1043,9 @@ dependencies = [ { name = "requests-toolbelt" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/64/af2af291aef5ea2b5fc56d9be151324eabb55c4ed89bc4f11343faa377b5/craft_store-3.3.0.tar.gz", hash = "sha256:02304296d7d5b896bb8d07660c3a29eaff461fc12d40fd2a21a39230f53c288b", size = 217863, upload-time = "2025-07-02T14:09:49.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/fd/2013bd5b54a5b14c69ba63ce1c2210ac15d26603b7b5a912b793312f3f30/craft_store-3.4.0.tar.gz", hash = "sha256:c2552f3e0596e1d63b3076ac49e87664571fb87fa45065dffacde4c395f4b28e", size = 322509, upload-time = "2026-07-16T12:23:06.131Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/81/14c738f7d95737a49d753e0b453c09bf8e9e096d54023200d27a6d58398a/craft_store-3.3.0-py3-none-any.whl", hash = "sha256:e1c8597653922f615db7d23cdc9b8774abc6b7cb18217252266ddbaa05a1c330", size = 52612, upload-time = "2025-07-02T14:09:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/8501f6d4254c71ce01541e7fb8938421669752ebae4b5b225ae5d97ebec9/craft_store-3.4.0-py3-none-any.whl", hash = "sha256:4ac8ce55a4e312c617d3300a5fb7a13f59486e9b6b9a78fe2cd768292adb7c3f", size = 61240, upload-time = "2026-07-16T12:23:04.77Z" }, ] [[package]] @@ -1161,16 +1155,16 @@ wheels = [ [[package]] name = "docker" -version = "7.1.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, { name = "requests" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/7f/731ff914b0255d3d065f45fd4e626d4b8c95dbcbaada049f337a6ac16410/docker-7.2.0.tar.gz", hash = "sha256:cebb93773d334f778e023a7ee352a8d6e13ab1bd3b863a4d4a59dec897df43ac", size = 118731, upload-time = "2026-07-09T14:53:46.39Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/23/529140fe1aab80fc6992f93a706deec709140a6397439139a054e1515c45/docker-7.2.0-py3-none-any.whl", hash = "sha256:a3f45fdeb9165e2d25d9a1d02ddf3bc70fb572cf5ebbf9b58558c22caf29b71f", size = 148775, upload-time = "2026-07-09T14:53:45.224Z" }, ] [[package]] @@ -1184,55 +1178,55 @@ wheels = [ [[package]] name = "dulwich" -version = "1.2.6" +version = "1.2.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/3d/7ea85d70d85f7d5ed5bf28dc742f106d8334e84286fbc852d983273dd890/dulwich-1.2.6.tar.gz", hash = "sha256:405cfd53a99374ff03aacdd7a86d6a07615feca072ed69721f49ae2ebaa3eab4", size = 1257895, upload-time = "2026-05-31T14:32:52.758Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/49/df8cf771b132981ca0d1d8229776994d87e403b610dbc606338657dc4fb1/dulwich-1.2.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9139d0110580a3038048286e761e9be166ec40a2eb19218b41b75541c5d87a86", size = 1401745, upload-time = "2026-05-31T14:31:52.714Z" }, - { url = "https://files.pythonhosted.org/packages/69/09/cc716d5f8cd4003786c32b2384e7d5626f706fd93a63d406a081e1bc4d93/dulwich-1.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cf80217e73a039614dde5ab2c74917833632912b788074bc7158058aafbf3e5", size = 1384507, upload-time = "2026-05-31T14:31:54.642Z" }, - { url = "https://files.pythonhosted.org/packages/73/ba/91fe15a707b5494081458af8c937025ffb6fabe866b8a1aefa9627534c56/dulwich-1.2.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fa7a089298fcbdaed493dd25c2f13574ccfc708f89a7aae8e3c25fd8393f5c81", size = 1473324, upload-time = "2026-05-31T14:31:56.38Z" }, - { url = "https://files.pythonhosted.org/packages/7b/b8/e2acf26d4ca5824f113fc0e3f6bc385ce63ef3fcd07acb5033b835760eae/dulwich-1.2.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6fcbb3dec5733898be2114476ff5abaa1dbb8a6d28ffbe492b3225a5a556197e", size = 1499519, upload-time = "2026-05-31T14:31:57.795Z" }, - { url = "https://files.pythonhosted.org/packages/1a/47/7117c70233e27b0413d154e88ed49235485ff3e07f710b269fdc3fa0e5be/dulwich-1.2.6-cp310-cp310-win32.whl", hash = "sha256:493e2ea0f23a8e9aae8e3000a366d1fbf0ed2c13eaf8f41863f050c6392ef138", size = 1068500, upload-time = "2026-05-31T14:31:59.045Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e7/62b3be2f19df7db367a1d5453132a946c91f974397f84b055106fa96fa8e/dulwich-1.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:72ac4f3fc92d54115ba2d812263117d9577b17f4c62ae8f170c177515f62e9d3", size = 1081079, upload-time = "2026-05-31T14:32:00.627Z" }, - { url = "https://files.pythonhosted.org/packages/37/ea/c54b0a87815e06baeb541c17e492c2e3fb7b9f216dc2033e3a356078270c/dulwich-1.2.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e103584421b7205f022bd413a324ff26905ffa84fcc1536f5787bf554d5d390b", size = 1400786, upload-time = "2026-05-31T14:32:01.994Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d1/5ca58eb2d1160d52ac2d109da1b4bd6c332a1a803fa6cb7ca7cda5f37431/dulwich-1.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e357d825b82e7fec2b83cd8e50f3c099c14c1070e1df961bfefb83943dc1582", size = 1383818, upload-time = "2026-05-31T14:32:03.444Z" }, - { url = "https://files.pythonhosted.org/packages/33/25/3dc9960cbdeef59fed4c07df52c17eacb3c5515b24cff64c524cdc75b563/dulwich-1.2.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:11b1f5a6a6075ab4f906dfb755c1d805c8c898ba4f4816b0fdb6123e113030ac", size = 1472506, upload-time = "2026-05-31T14:32:04.885Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/39dd7470d37609a62c66bb59d298f871fd835d37580cc870c5b8a66ea87e/dulwich-1.2.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6d9720d591052730775dcbf450f0cd5b35162f4eeb4754337a5d763326481b2f", size = 1499334, upload-time = "2026-05-31T14:32:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/48/f9/504b6e0c9f26bca62dc54cf5d6a65c807098856b6732279e37a9c034acab/dulwich-1.2.6-cp311-cp311-win32.whl", hash = "sha256:371394e2c6f3f9789cdc0abb965dae9bc62e79984b84f35339e9d466598c9fb0", size = 1068084, upload-time = "2026-05-31T14:32:07.466Z" }, - { url = "https://files.pythonhosted.org/packages/15/b6/ee75e1916984716cb57adf0d1f95e7b241d4accc4dc4d1ae3a9ddba1a411/dulwich-1.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:f887643cf1c7a04e898547bd9f0acf6654d772ebd153012433ef950315dcf776", size = 1081023, upload-time = "2026-05-31T14:32:08.889Z" }, - { url = "https://files.pythonhosted.org/packages/d9/80/496b2f8d584a7ba28519fd552d10c070498a76a17a92d288f1263e8e577d/dulwich-1.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:116ac7decb923a473540bf813c1ceb061bef07209fad5fb002d867f1907f9393", size = 1398028, upload-time = "2026-05-31T14:32:10.25Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ce/2ac1ccb8f5c039a93b3e6c1fc9f06ded05eb4adf7e934a643894389be755/dulwich-1.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6993ad48f92dc38a43e3c1bf25efb03a62fc2cf4db86a2e904b6c7176dafc3d5", size = 1336335, upload-time = "2026-05-31T14:32:11.64Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7d/7e1e736b9dfdb8aacf474c29d2b2fe331a23e1aa3741428a87280a73dbb9/dulwich-1.2.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:72512e2a22df6fb65ba7b66f5037046019a12343f6e9e54f42bcc4a68ab3d628", size = 1418127, upload-time = "2026-05-31T14:32:13.124Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/fc716cd97d35335a6bc02bea4e24bb1c1f0947731a421c2038b6c250c0b2/dulwich-1.2.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e995ad77b0685747bdb51f7a5cd7e6cb8efe73e29517b0f2c95fc2e6d10d5a90", size = 1442869, upload-time = "2026-05-31T14:32:14.532Z" }, - { url = "https://files.pythonhosted.org/packages/67/35/79dcfbafa5d3c6271da71a6b016e80f1a393a50c013958fd1d7c3375f284/dulwich-1.2.6-cp312-cp312-win32.whl", hash = "sha256:4940fbf7cb37870686c63dfc7682e1afdab0e55b663bb614572909b68e775d31", size = 1018595, upload-time = "2026-05-31T14:32:15.805Z" }, - { url = "https://files.pythonhosted.org/packages/63/9a/b33d7e6749417552fbf065fd734395a90b7b5d27a377149fbb837aea8127/dulwich-1.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c60ddc8206e04e8e08208eac80130004eff0d587c82d398beeca7330cade061f", size = 1033271, upload-time = "2026-05-31T14:32:17.27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ea/f0d0aaf7c9e36f5490579a20ed37c85afd19e90177c2e270ff533d7fd533/dulwich-1.2.6-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:cdd15b8442b527575d733d90cfd6d3c4cbaebf989e2298b0cb57a7916c66254f", size = 1532486, upload-time = "2026-05-31T14:32:18.668Z" }, - { url = "https://files.pythonhosted.org/packages/14/4e/5c212c2dcc2d8c06cafdcdc7893d9516cb861ec277f25a0f058f98512d22/dulwich-1.2.6-cp313-cp313-android_21_x86_64.whl", hash = "sha256:dd2783352917b7cb3ab12b7c3f7757210d93af6df0bd2d876a8e5b53b2feb3eb", size = 1525768, upload-time = "2026-05-31T14:32:20.174Z" }, - { url = "https://files.pythonhosted.org/packages/f4/84/7ff849d4fe769cb6439fc50322381b7eb3d6e5d64da9e5d8337c985a7748/dulwich-1.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:204d14692fb1dd850ab773690f7530f4065f405e9e7dd3f85bdf92e9330ffa2d", size = 1396354, upload-time = "2026-05-31T14:32:21.52Z" }, - { url = "https://files.pythonhosted.org/packages/29/4d/2cb9662dd57417a11e5828f2b8f7607cfaccb42dcd9b6d69316755a5f5e0/dulwich-1.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:21e2e9b81ab04ad83f2d4101ac515ef56ee08d06fd853c1a7ac255f20bb49963", size = 1335031, upload-time = "2026-05-31T14:32:22.978Z" }, - { url = "https://files.pythonhosted.org/packages/2e/82/38ccfa7ee30c13d44734c5b1eb92ad0c95a96035319040e2c0b2011eee75/dulwich-1.2.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7b4a2f497718bfe1a3b21f933ee27c111b9cea560c0b2d8a6d939e1b5f297f79", size = 1417366, upload-time = "2026-05-31T14:32:24.295Z" }, - { url = "https://files.pythonhosted.org/packages/be/a1/239d52cbd94482c064821a0ccece888aac105a81f3f9719b9622c52fe6ec/dulwich-1.2.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5ff9f36c95deaf7eb5d6ccde4c68adbcb932a87e03c1b479a8d94d779e7cc5d2", size = 1442588, upload-time = "2026-05-31T14:32:25.731Z" }, - { url = "https://files.pythonhosted.org/packages/6e/92/739dc9e4d5da0b1c09b752f8aad94a518ff5eca7db2f5039ba7d5976b81f/dulwich-1.2.6-cp313-cp313-win32.whl", hash = "sha256:04252b107a1600325f5f0301dde8b5b62f5bb51a0467e360070baddbb4edcea7", size = 1018371, upload-time = "2026-05-31T14:32:27.147Z" }, - { url = "https://files.pythonhosted.org/packages/bb/28/626dc722ab20e0e5d0bf67e212494563f514790bc9c4b7c0133fdf491a5d/dulwich-1.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:6fd9911fb57ee2d6eefaf895df65e1139fbc911fa560e959b38feabe5f15003f", size = 1032986, upload-time = "2026-05-31T14:32:28.66Z" }, - { url = "https://files.pythonhosted.org/packages/5c/b9/bb2f8b1d668b3959b8471c2af0fa2dbce66e81b2defab1fcfde4cd5a0ac1/dulwich-1.2.6-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:cb1f8d658f36b2ac3982715dc3e49f0d741a3e5a8c40136bebb6d8493968aa12", size = 1533055, upload-time = "2026-05-31T14:32:30.048Z" }, - { url = "https://files.pythonhosted.org/packages/e0/66/1a3b4580af650a40adb6ef1dce6f9153fa1096c723e1abff32959a0a2c76/dulwich-1.2.6-cp314-cp314-android_24_x86_64.whl", hash = "sha256:ad4b6114440f9cf72315b173532ee3284f27a288b8a24bc27e45b2e54593720d", size = 1478589, upload-time = "2026-05-31T14:32:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/a3/34/55bc2440d1924710390ece68dc30496b0a0ef26228d9e02a60c82fc15ed1/dulwich-1.2.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:824b7f5b22b128c1e1ad7c655e9790e2d75c7ab1ba1e40a708024193f1dc47a3", size = 1351518, upload-time = "2026-05-31T14:32:32.644Z" }, - { url = "https://files.pythonhosted.org/packages/f3/31/befeb51ffd5047ea090ab18d2dc43525e6d528006eb7064135c2f1a1229a/dulwich-1.2.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c187efaebb72146245ebcb872b89fdc99314fa37442119c5a5feb18af3f4b8a", size = 1335713, upload-time = "2026-05-31T14:32:33.995Z" }, - { url = "https://files.pythonhosted.org/packages/72/8f/2913d2ab1cff9722383b88be0e34f8a277075c5b5ca3fc822ac2192476d8/dulwich-1.2.6-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:79728d98e0ec184856d71fd0d55abbf5ac7345b5baea9f2d1533a4de9064e13d", size = 1420343, upload-time = "2026-05-31T14:32:35.57Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/53afa6cf1fb3bdf6669a73a6b1978f0adc2ac24f03fc414f45c5902e66e7/dulwich-1.2.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c639a8c9fb7e745749f2dcbd5b63a82df2fc99cfe62e2c3654ec025a42d2e51f", size = 1442317, upload-time = "2026-05-31T14:32:37.216Z" }, - { url = "https://files.pythonhosted.org/packages/23/34/4119559608dfc4b68427d8e9e3892db7a4b2dbf7b3e7c5b6a390e0af560f/dulwich-1.2.6-cp314-cp314-win32.whl", hash = "sha256:dd2b66c915f1b22ca6533b48e8ee435800b25f74f419c40e1a92271666d8b297", size = 1026443, upload-time = "2026-05-31T14:32:39.064Z" }, - { url = "https://files.pythonhosted.org/packages/1e/5f/4de7960cc39cfbd629ad60c1ef631ed32e4b303d4228ad8f92847aa87f3e/dulwich-1.2.6-cp314-cp314-win_amd64.whl", hash = "sha256:82e8810e57f9651a624116e3fede33276f89406cb910f517b944105e284e6755", size = 1041672, upload-time = "2026-05-31T14:32:41.008Z" }, - { url = "https://files.pythonhosted.org/packages/72/19/01bcd25e4b2a8cf5f3507c4d02d6c999b70eb6d32eaff59c583e8e9c40c0/dulwich-1.2.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:794a85b8b9d4ad57d02c8cb455735419ac50c0f2e3d26d83873e34abee58cb1b", size = 1350601, upload-time = "2026-05-31T14:32:42.288Z" }, - { url = "https://files.pythonhosted.org/packages/14/9c/517d858ee9af0678e5605397d1b5ca6c49210b64508c91486893c9fc0e56/dulwich-1.2.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb27a9ebe9029c872abadf4f9dcb18c9f6a4b7a4afe137f79a61df1ae59dc6bf", size = 1334723, upload-time = "2026-05-31T14:32:44.163Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/d8dd703c6c581eacf109427472c1c02955b8de70fb8154ad2fe0cf2e0540/dulwich-1.2.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:1c35c294acfc5a0a88d01d5db1abeba550bf6274bcc3fddbf8b365e9eea280da", size = 1461300, upload-time = "2026-05-31T14:32:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/df/40/94c0d609206a770d3b4bc8dc30601e3c63642af637c5c12035e9645e0c85/dulwich-1.2.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:f682671a2e19b7b4caa572ff3073557de049a153946305e051a4f50bb0e5e1bd", size = 1491547, upload-time = "2026-05-31T14:32:47.12Z" }, - { url = "https://files.pythonhosted.org/packages/04/97/c18ab70ec27eed02f6ba7b00b3d6236657df7cabbc7175533ff5b62f2147/dulwich-1.2.6-cp314-cp314t-win32.whl", hash = "sha256:27db364f2f3cf5b0dddd44d6c2ae9a20f6021e2bae8b1268fa689076f0192244", size = 1024467, upload-time = "2026-05-31T14:32:48.476Z" }, - { url = "https://files.pythonhosted.org/packages/41/8c/7f49d256a813ab799fbce519a37857d75201c7c02b06b1a65fd989ec2ff2/dulwich-1.2.6-cp314-cp314t-win_amd64.whl", hash = "sha256:fae59c5e345f5ca234c85d157f1c7d5e0086126b45b5f7cfa66ffe41d049fdd6", size = 1083682, upload-time = "2026-05-31T14:32:49.881Z" }, - { url = "https://files.pythonhosted.org/packages/24/15/61bd455d33979584f19d3a6e0b49b49e0d891bc680fc8cc7b028aea7360d/dulwich-1.2.6-py3-none-any.whl", hash = "sha256:8d8175dbe4feaf62bcafc8708448bfe223b4dfc71609be25c0cf2b0962abc36c", size = 688260, upload-time = "2026-05-31T14:32:51.285Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/89/d9/e6272430ccf84739509f45006543a1babcecc55a70cbd296424901df8ed5/dulwich-1.2.10.tar.gz", hash = "sha256:106546960f592216315e56d32a92e72e67f1e86671335722f833f5ecd5b73314", size = 1310282, upload-time = "2026-07-07T00:52:05.986Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/fb/8928fc911e872ded288ca6258f85dabd3d8aa7de35e8dccfb7bf37ef59be/dulwich-1.2.10-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:07f81ec4773c89d5989d173a077ed54b400719b4990b2f46a7b5604f16da46d1", size = 1379546, upload-time = "2026-07-07T00:51:00.818Z" }, + { url = "https://files.pythonhosted.org/packages/29/bd/9c8a1ff7fec58a74eb36ec54d124cf88b5ade65aea1fd612b82e791480bc/dulwich-1.2.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:afb850b4f6ff6f1c7178bfbefbf079a0d0d03014bb385929a0934732ac7dd0c0", size = 1373414, upload-time = "2026-07-07T00:51:02.764Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/0f7869d034766f65579e56705854c160020966238360b7583f8fa972b8a8/dulwich-1.2.10-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:86c81a30340ca15aa1d275f42abc1f297e0af2a2d6cc213b5e9e40f52c61ff4f", size = 1452967, upload-time = "2026-07-07T00:51:04.459Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e1/93dc57b7fbd3efc3fc7f57d8a094fd06f4372df8cbb9d5a23972147c354d/dulwich-1.2.10-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:51d97aaa382b6e9ee0adffcd0028c430ff133be4d6bbff53ee1aeedcb3fcff07", size = 1477046, upload-time = "2026-07-07T00:51:05.991Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1d/8fa893a9249bdf60790a5ebc012c367943684d0f65eabe4c2cb2576e5222/dulwich-1.2.10-cp310-cp310-win32.whl", hash = "sha256:93299fd523892d39f69e08bf71984267798d76cb914163c48c998181891a24a2", size = 1043829, upload-time = "2026-07-07T00:51:07.309Z" }, + { url = "https://files.pythonhosted.org/packages/5a/51/bf90a2b83eb67c2b2f125d39941ce20b4cdfeb7d41d89d1cc699f2dac474/dulwich-1.2.10-cp310-cp310-win_amd64.whl", hash = "sha256:f91eb2908d900288da261f05fb436a384fc94b30980869a2db4db7b52b30047a", size = 1061358, upload-time = "2026-07-07T00:51:08.761Z" }, + { url = "https://files.pythonhosted.org/packages/55/11/8a2aa5f7220d9290e4c9ad4ac6d482d9b34762079331e8e02d835329f552/dulwich-1.2.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f6d989605955bfbb678425a2e8710100ef35960340bfb55faaca902006c69163", size = 1378353, upload-time = "2026-07-07T00:51:10.307Z" }, + { url = "https://files.pythonhosted.org/packages/7a/66/859b121c1f18fa32833a239beadd67cf11df89974ff223b28d9ff53f4f2f/dulwich-1.2.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af4a750597d1a6e77bca74b9246bf84b62a9bfa7e48783af4ed0e9f04f0dbfbd", size = 1371855, upload-time = "2026-07-07T00:51:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/1833afc2444fc70e6e79458169275687aef858ea1c6a8198e2b7f97da27d/dulwich-1.2.10-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6c715289e8c5667978ff28a1847736633766ca1c7082144ba0ee95e459be3f97", size = 1493934, upload-time = "2026-07-07T00:51:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1f/a200b9071a4d9dce6ce0033654d63ee389be2d4de432d1d7028dccdbf8d5/dulwich-1.2.10-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f21c7f2dc4de8353b59b82bc0245838f3f94ff9332b71f6394642e3bb012c71b", size = 1523369, upload-time = "2026-07-07T00:51:15.338Z" }, + { url = "https://files.pythonhosted.org/packages/11/1b/e36deccfa78f674e7c4d7482b2a710581a1161b65080dd6b1daac17c5b42/dulwich-1.2.10-cp311-cp311-win32.whl", hash = "sha256:ca1fbe5f4b2ee48154b24c68eeb689b848394a857f7cb3dea3de0b86c464454b", size = 1043413, upload-time = "2026-07-07T00:51:16.9Z" }, + { url = "https://files.pythonhosted.org/packages/fc/67/fb78d06d58486ea3bef2c87af17ea0b106d9863e89664cc834cea060af6e/dulwich-1.2.10-cp311-cp311-win_amd64.whl", hash = "sha256:9874964ac78713c640c42c9dc27a74dd2d4169d99821a57301d37e5673409d27", size = 1103445, upload-time = "2026-07-07T00:51:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/73/fb/af9504fed5f023194143bd059132729aad00f4608f4ed785dc1f94ff500d/dulwich-1.2.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4bf4671061aa98e7f4d420fe5238b2ca2cb2a00a280c8f549fa92d34569971a8", size = 1371347, upload-time = "2026-07-07T00:51:20.811Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3e/aff9e8b9d07f3afac9c618cd2e7529e4695375a64ebd07831462bb78c946/dulwich-1.2.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43315f1e95f7d5c1a6f2695f5534263b80678ccbfd9756101f07759a4e65850e", size = 1356141, upload-time = "2026-07-07T00:51:22.108Z" }, + { url = "https://files.pythonhosted.org/packages/89/8d/03c2692896241b94d04cbc8f4db662458864e13604e5295baf96a2b647db/dulwich-1.2.10-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:699edc94fddd0a2346e41e405fd7c537072d75cb9ddb39bd47f2e8fbd56d1e4e", size = 1437999, upload-time = "2026-07-07T00:51:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/43/75/b37e1c9548d7b9355971e462acf5da795c9539d8b330f4495ddf0244d99a/dulwich-1.2.10-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dc7c46cfeb40022d2fca9d23c3ca1aba602ee99bfb6554278f05f2aa890a0e", size = 1463581, upload-time = "2026-07-07T00:51:25.06Z" }, + { url = "https://files.pythonhosted.org/packages/32/a0/2de9d8bfd975b79e51c40394d8fb627dbffc97a4c89778eb3314b304ea6e/dulwich-1.2.10-cp312-cp312-win32.whl", hash = "sha256:9c01a6b29cedb054a16fa04adf834a6531a39450888fc7ce2b3bf0fb6cba70c4", size = 1082677, upload-time = "2026-07-07T00:51:26.578Z" }, + { url = "https://files.pythonhosted.org/packages/d1/67/49e913c54d7723c5a5bb12a38552a6db5cb691631326882ab2208a5186fa/dulwich-1.2.10-cp312-cp312-win_amd64.whl", hash = "sha256:96981c6704def4fd0f91fb9be9f613cf99ba327e2a655b8ef987f5f5245420f3", size = 1053703, upload-time = "2026-07-07T00:51:28.023Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/6389b4209788fe46e4943492158f497ac6f7c485b83c70a4f79546d0ccc5/dulwich-1.2.10-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:bd859654ed2e8c61c03066b739fc758b70edfcfce41344fae07f5f7f41b998b4", size = 1510472, upload-time = "2026-07-07T00:51:29.709Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5c/07c64540fc713057351db05a021deec6518f57e8393e734d058be8def427/dulwich-1.2.10-cp313-cp313-android_24_x86_64.whl", hash = "sha256:adf9a780add1b6da7e6df1b609afca66733380188a3a257ea340bc080ef4637c", size = 1500010, upload-time = "2026-07-07T00:51:31.092Z" }, + { url = "https://files.pythonhosted.org/packages/20/af/4007763bde53f768ac9736f81f5af2658808181024d2afe1b99e6bb34826/dulwich-1.2.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f337fd7dffe5e6cbff5e9a4694339b7575ed948babcc7675321efac075cb0a0e", size = 1370987, upload-time = "2026-07-07T00:51:32.553Z" }, + { url = "https://files.pythonhosted.org/packages/23/da/336440eb9bf6ca889a99c0c1bab37763714e2f56fe9553fe23f269560c58/dulwich-1.2.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ded6744fcd2fb0e1f66c811492aefcdbee169010057f2a2e034a463eb0118c80", size = 1355513, upload-time = "2026-07-07T00:51:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e4/cd27a143488489c4d40b3b9fefa2fa189c49920ac226c6b11475892f6576/dulwich-1.2.10-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a16ea1a6d96db53a0089f7f6c4e6b942eff143e4de26b786aa894bf978ac148d", size = 1437157, upload-time = "2026-07-07T00:51:35.691Z" }, + { url = "https://files.pythonhosted.org/packages/45/03/44017e9b958238612aa17f8a6ad9af4ff63536cb99c8d8f4e210a8aa6866/dulwich-1.2.10-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:dfe2683285a7363a0d4b16f7f6fc9ee8a57a7b1cb1c390695475b76d8ce1b0f5", size = 1462033, upload-time = "2026-07-07T00:51:37.119Z" }, + { url = "https://files.pythonhosted.org/packages/75/74/6f2ddceed5d8fa3f49235fe33afd55d92a986a3bd5ce3dbcba42907d96d4/dulwich-1.2.10-cp313-cp313-win32.whl", hash = "sha256:0840968ec6636a677b48bc6e38bf4ce2326e8f0d60cd9c9753cceeff5e23e92c", size = 1082412, upload-time = "2026-07-07T00:51:38.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/bf8c3e47edb11798db7303b255d63db916877042e848808a6b5942e6624e/dulwich-1.2.10-cp313-cp313-win_amd64.whl", hash = "sha256:a2e884a93452e8e1fb4a4886d14b328bae65c7a2fa88466c0b38faa63204f34d", size = 1053583, upload-time = "2026-07-07T00:51:39.95Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2d/643fcf28fb2f3118ec6b558695e4e18b0991edf64d4447bc6c5105465d4f/dulwich-1.2.10-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:fd86069664383fc456eeb291d67dc541c7f6ab163c59da1f8e18c72283f120c5", size = 1512387, upload-time = "2026-07-07T00:51:41.629Z" }, + { url = "https://files.pythonhosted.org/packages/b7/61/c57fd318bc943f294420353fa5544d54fb8984abdd95ef24a21d8d0f5b3d/dulwich-1.2.10-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e445229de65ad6c3a8e2d9934fb807453c09b94f1469d32804e19fdfdcf6fd94", size = 1502540, upload-time = "2026-07-07T00:51:43.571Z" }, + { url = "https://files.pythonhosted.org/packages/03/8e/4b284074e199193dbc1cd8d26dcc92ab8f84fb73654c05c290619cd3ed24/dulwich-1.2.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:09c36bed6f2695cf34e606c403a00c6511f87f31ed20cac68f0bacf6df21cc4b", size = 1375734, upload-time = "2026-07-07T00:51:45.642Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6c/b465e1b60ab4675cbeeb8977c871c4b773bf0667d1a33dfa879e446c86c8/dulwich-1.2.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea42aa414a53d71ae0e294003a10b17ad454ff73d345a82e47da93e977fd2ff1", size = 1358916, upload-time = "2026-07-07T00:51:47.098Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e8/7efcc79c58153dff1c96dc8992a161d28288a7381eb4591c49712d112f61/dulwich-1.2.10-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:5b60380225742d7292703e573ce5a86be6f69fb9088ef2965c43830479df9f90", size = 1485761, upload-time = "2026-07-07T00:51:48.407Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f7/609f2fcfafc515f25948f14c349467d2c3c72a035e65470a8cc4fa4fa7da/dulwich-1.2.10-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c321650fc9fa0b3e614607f0a229523491153d0ab00502366ac93b022d3c7f6c", size = 1513339, upload-time = "2026-07-07T00:51:49.884Z" }, + { url = "https://files.pythonhosted.org/packages/90/53/df8b6c6168e7e1bab5b33dbee0c82d99ab94021493e5d1f1445d15eb1c61/dulwich-1.2.10-cp314-cp314-win32.whl", hash = "sha256:834df23dca29e5adc5310b1b73eb573ad805fd0f39987e86afda2d81bfe24ab1", size = 1046718, upload-time = "2026-07-07T00:51:51.403Z" }, + { url = "https://files.pythonhosted.org/packages/5a/da/5497cc3be68fa62626da0111b88dfa1a296907fe67c3cd57ff65bd67c7bf/dulwich-1.2.10-cp314-cp314-win_amd64.whl", hash = "sha256:abd8ffeec9c151cea62aafd25260ac7141a7ea56ce363dfc72cddf63396e8d02", size = 1106937, upload-time = "2026-07-07T00:51:52.853Z" }, + { url = "https://files.pythonhosted.org/packages/1e/af/b88671fdc6cb3f0732d0bef0bfdc2937370dcef605c14d90d2d63f7b7c34/dulwich-1.2.10-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d5fd557539c982aabe15f7a14cff3d8786cdb62578d6f90f7871379fa3453260", size = 1421051, upload-time = "2026-07-07T00:51:55.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/0d/0d5d1f92c3589772d6f367be9251c417e35890ca7b5e461ed90f11e1615f/dulwich-1.2.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:66cb00f1298e4e0dd254ff6a68554577020b4b810b4a31083409baea62f8b1df", size = 1359050, upload-time = "2026-07-07T00:51:57.002Z" }, + { url = "https://files.pythonhosted.org/packages/bf/23/0b1a354cbaa574596e0bde8171b452ec7882765fcef283dcd2f4d8bf93b0/dulwich-1.2.10-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:eca947467a28992088a0461c408791b405153294887accb92f64d60f9cc04758", size = 1440306, upload-time = "2026-07-07T00:51:58.39Z" }, + { url = "https://files.pythonhosted.org/packages/e6/14/0178b5f8a9ccaa7ae434d0c931d2c792b1243a1ea2d98a08646a936f132e/dulwich-1.2.10-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7712da64d7cad219c77aa4216d46a1b14876ed078b2a88621462d2834f4f85b3", size = 1466689, upload-time = "2026-07-07T00:51:59.738Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ac/10ba0dbe9cf3112d3b6fc8305b731798734017762200ac9fb75a0fc297be/dulwich-1.2.10-cp314-cp314t-win32.whl", hash = "sha256:52cfbd70f178b5ad770b9a1aee8a757aeda0e7282ae72cd546a98e6b9b0816cb", size = 1046430, upload-time = "2026-07-07T00:52:01.532Z" }, + { url = "https://files.pythonhosted.org/packages/34/d3/2bc63e9ef4a3248595b8f02bfee22bf9df77adb181a53716fd81305d2665/dulwich-1.2.10-cp314-cp314t-win_amd64.whl", hash = "sha256:38478aa5d1821fd3d41e1ec7c65fb9a55d302d041635079db11118b79109054f", size = 1063788, upload-time = "2026-07-07T00:52:03.026Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f5/efdcbd50a4480083d8d049ea1c49095097802599f7ecd2dc92942af7f4c4/dulwich-1.2.10-py3-none-any.whl", hash = "sha256:b9f18afbe8e6f6a380b199df3d2da14312b4eac4ee34ee77c6f61c56bd2693ed", size = 711548, upload-time = "2026-07-07T00:52:04.539Z" }, ] [[package]] @@ -1258,11 +1252,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.4" +version = "3.29.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, + { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, ] [[package]] @@ -1344,14 +1338,14 @@ wheels = [ [[package]] name = "httplib2" -version = "0.31.2" +version = "0.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/f5/ccf58de92d61e3ad921119668f54ed36ca1d0cf5dcc5c1657dfb164fd78b/httplib2-0.32.0.tar.gz", hash = "sha256:48a0ef30a42db65d8f3399045e1d09ab0ba66e3b9efc360d07f80ea55d286025", size = 254283, upload-time = "2026-06-26T10:13:56.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/33/a0/550eec327e5f5c7b732531c489f5307efec41f047b0d703bd4ca1e5ad2db/httplib2-0.32.0-py3-none-any.whl", hash = "sha256:dc6705cacdf3fb0a2aba7629fa33c90fd93e30035db0c157325826be177e4816", size = 93148, upload-time = "2026-06-26T10:13:54.985Z" }, ] [[package]] @@ -1371,24 +1365,80 @@ wheels = [ [[package]] name = "humanize" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/ea/13a1ef3c12d12662905801495283530251918b70d62d368f1d2e0272c70d/humanize-4.16.0.tar.gz", hash = "sha256:7dc2244a2f84a4bfb1d36c37bac80cd78e35cdc5c119206d87b018e1445f3a3f", size = 89515, upload-time = "2026-06-30T16:17:29.859Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, + { url = "https://files.pythonhosted.org/packages/b0/aa/0b7365d30fed43e7a3449aba1fe20a0a7174d9cf13e282af4e69ac825441/humanize-4.16.0-py3-none-any.whl", hash = "sha256:353eb2f34c09d098b2880eee8bef21832eae6d174f48c5762fff7e5fcb74d01d", size = 137209, upload-time = "2026-06-30T16:17:28.36Z" }, ] [[package]] name = "hypothesis" -version = "6.155.3" +version = "6.156.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/77/13ec9b6390bce44f5badab39837dd6789bbfe6342a2ac611a71537a7756f/hypothesis-6.155.3.tar.gz", hash = "sha256:1e34b17ae9873515384312cb7640abd773eb096c7eef8c0d9c614fa2c306e9bb", size = 477961, upload-time = "2026-06-16T00:33:23.273Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/23/ce3a543935a01e478349e82f6c1440776f92d4cb346662c4d81574878fed/hypothesis-6.155.3-py3-none-any.whl", hash = "sha256:ede5a3d142d9c5c9f70cb3075541905b228d6c3a682bcec3d4fe0722e9eda127", size = 544401, upload-time = "2026-06-16T00:33:20.497Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/51/9e/de15052a56c80aa7f4dd677a69cdb22b82df2b53a7f5d0c3fc45b6d5d80b/hypothesis-6.156.5.tar.gz", hash = "sha256:b2780ef6f5bb2b9775763b1d2f978e3cbe31717303c3eb9aa2ada2531c30caac", size = 476278, upload-time = "2026-07-10T14:22:30.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/3c/4e6a1caf42984037e1841e9552b06db96fb26852e1e708a6ddb84b7f3c07/hypothesis-6.156.5-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:aec9566fcf6f062c0730ca864491e888b5a3fd2a6e5652312d41ec700ddd1c13", size = 747969, upload-time = "2026-07-10T14:21:42.536Z" }, + { url = "https://files.pythonhosted.org/packages/73/2f/2898585f3ec2b6e67b8bef9e5293ff4b8a88a1c606987aca29363feae2c3/hypothesis-6.156.5-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:cda44f5da66670907004b21927bcf5d54073f2e502775f42921251ff03e5db4f", size = 743018, upload-time = "2026-07-10T14:22:10.027Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d0/261e73324d8bf887941a332b7d155ee2c2d1bb0bd8dad535569a6b260e8c/hypothesis-6.156.5-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04fc5fb72398f32789c571c542bdcd5ef532dc584b75e24d72cdb27949e64c3a", size = 1070135, upload-time = "2026-07-10T14:21:48.44Z" }, + { url = "https://files.pythonhosted.org/packages/39/02/e5e3a2e5be4b4b6b62fe8002adeeb1b8178fae5722824bc1d889ffe36c72/hypothesis-6.156.5-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a70e62f189a8cc05cbdd9a30e093a6b6ca27a805cff567c0cee834a5982200e9", size = 1121731, upload-time = "2026-07-10T14:21:14.745Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/83c4bae9fa63b304e36c379bd7cb3dab4ff8cfd075f14bedd883cdc7146d/hypothesis-6.156.5-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:910dfa8f5b1adc259a4dd73cb06666f632bad2938fb3516d363041d6a14a1b7b", size = 1111410, upload-time = "2026-07-10T14:21:16.199Z" }, + { url = "https://files.pythonhosted.org/packages/d7/3e/ff4621e85fedbfa329c1c4e9ede6cb71a8a12bc93a8e1dfe7e045e50464b/hypothesis-6.156.5-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:78bd5d844ebc49c950596ee50e7afca0da64419b85c382699469a55b65024f6b", size = 1244914, upload-time = "2026-07-10T14:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/66/64/de3d3a8be8eb0a2d50c10ddbd92bcc8e8ddab6c1e01c81d081cf4f25aef1/hypothesis-6.156.5-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fafe8e4e62aeb73c1be0f71ccd1b387e701ccbedbde474702da8e4bce01a54e6", size = 1288779, upload-time = "2026-07-10T14:21:43.9Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3c/6767aac371738462e737b08423d7593014943b3d3b75436ed24cb6f35892/hypothesis-6.156.5-cp310-abi3-win32.whl", hash = "sha256:4552dbf25653e3dbdd8dc594a77732c0370ea8ba52c2df64f48a338e63107118", size = 634831, upload-time = "2026-07-10T14:21:12.265Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/dfde7b1b5d7a0d63325b1009c13910f1e1bd77c2a80162bb95e1ee767c04/hypothesis-6.156.5-cp310-abi3-win_amd64.whl", hash = "sha256:cf647a0d054711e42004004b37376885a50e1b76e5f02c781a3ce674c1e13d5a", size = 640369, upload-time = "2026-07-10T14:21:56.725Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a0/118d95492ee543db35b83dbcb375e3d6f306fbfd1545e498a71d7337f3ee/hypothesis-6.156.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b681b79736cedfa27f7592ed0cea78c217603d3dfe2feba05d61df1f5e468da8", size = 749285, upload-time = "2026-07-10T14:22:21.721Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ea/5f3bdb9a3172f610fe7f55d33e9668d06e0a17843c03427b04c056861f0b/hypothesis-6.156.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb3ecb0cbf7ed4f139049a123e15a043783ff659e55d7036770b975b794afac7", size = 743938, upload-time = "2026-07-10T14:21:17.644Z" }, + { url = "https://files.pythonhosted.org/packages/81/cd/165c315ef934e884d25e9352a17076209cb43fbae71bf81c68d3f4b8d11a/hypothesis-6.156.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed1f15a9c308c87758b4a7a1d062565424da63e1f02f1c60dc56f3ed2b1deeaf", size = 1070842, upload-time = "2026-07-10T14:21:28.113Z" }, + { url = "https://files.pythonhosted.org/packages/27/1c/cb61aa4593e7adbfeb3fc5724f77eb0bda060bc643aae4fce474a1a7dfbb/hypothesis-6.156.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e2f40f5dabe72c3c5929d77588679ee2a0ee076e9ad3059ef52ae17be6f848a", size = 1122159, upload-time = "2026-07-10T14:21:13.487Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/c39bf3c9beb180671d9a1913e3de2014dea57095834f78b9dbcb51d689be/hypothesis-6.156.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de9fe227fda3c4de32d76085be8a021af3bb815aa95f9ab54b6df16d8a56fe23", size = 1246010, upload-time = "2026-07-10T14:22:28.383Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/c92f83de3f07178ec1e5eb65ded3297f33be20fa02bec084c476c69c45b5/hypothesis-6.156.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c3e90eb498f1c5954a1768dc34c1f10a0808b37f7e6e3a99ae336ebe2e4798ba", size = 1289452, upload-time = "2026-07-10T14:22:06.256Z" }, + { url = "https://files.pythonhosted.org/packages/ff/75/9ccfff5cee45fce3247ada6b39d7ee166a5aa9956a262bc60abb52d267fd/hypothesis-6.156.5-cp310-cp310-win_amd64.whl", hash = "sha256:96dc14d844e316c7607d330628bf68e220a601cf9a36c4eb6f57322731b26284", size = 640330, upload-time = "2026-07-10T14:22:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d8/ca43184af179fff81c4a8980f13b146e6303652bea011c0ebe5d8199c13a/hypothesis-6.156.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9c7de49e0567fb956f38c2d469b766365994bf2b23df652df6610cb6ca7bcaca", size = 748942, upload-time = "2026-07-10T14:22:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ea/869c696ec062ea99e2d08fbb50b07fd2a5bf49d3b0370c6200bb80e7f2a2/hypothesis-6.156.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81207bba26a7e616474acf4ba1e69d53fbe0aa44db7915f02ccd5112da0c5578", size = 743700, upload-time = "2026-07-10T14:21:50.161Z" }, + { url = "https://files.pythonhosted.org/packages/30/ef/80e5cca86e186363872dd49a3e355879b47f36716f46a980cb7c4f3907a9/hypothesis-6.156.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03f1919fa139c86fad6efcb8be3b6614e5309b67f197216d21b59337e2fd04b", size = 1070726, upload-time = "2026-07-10T14:21:36.73Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/15b15ea52dae1fff4623abe50236c86f52fff4a395d27781c7e1ddb6cdd3/hypothesis-6.156.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d5fbbccaeae692f3cd9c32fd946fed49881385c16d46072b445d2d01b33858c", size = 1121953, upload-time = "2026-07-10T14:21:33.374Z" }, + { url = "https://files.pythonhosted.org/packages/7f/39/03b7b442f549b64871c1aa4847a0adc5ed5bd003b4b4659277c04c477828/hypothesis-6.156.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5cee554b4d0c876215a6bd3bb68d676df9b3666b642a5b952c5c6d777dca6b1a", size = 1245534, upload-time = "2026-07-10T14:21:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/82/c5/958e9d80a36b0e76ff917e6e0d35c864c0b767a5a713d13cd462911596a8/hypothesis-6.156.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:949c9e0fdf0632ab9ac119b99a3b6e610434b5185e86ededa4df0f83f3df4263", size = 1289127, upload-time = "2026-07-10T14:22:04.605Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/7b922c5ba41c4e10239354ad3232e4c8cd7d7c75fee170068bc28f1baedb/hypothesis-6.156.5-cp311-cp311-win_amd64.whl", hash = "sha256:717f167a5fd61097fcf1d31eab2889292f464398b3b1e306c99be2dc91de47ca", size = 640163, upload-time = "2026-07-10T14:21:58.166Z" }, + { url = "https://files.pythonhosted.org/packages/33/ae/f6663fb752e2c186300c4c3f039ac19182382721d5bfb3370ea8820d02f0/hypothesis-6.156.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0622846641949d0f1a925f458768be223b55a8f060b059734f637f3a00869a43", size = 748814, upload-time = "2026-07-10T14:22:11.598Z" }, + { url = "https://files.pythonhosted.org/packages/d0/57/93430b874ac895caa42ff3463e2a5a4e6d3d73c7a8674f8739a0fb5e2707/hypothesis-6.156.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94f6a94269483a7a53335fa5df2ae2f7c95f76e6a953f511fff03859c2fe616f", size = 741907, upload-time = "2026-07-10T14:21:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/b4/1c/2cc4764ff9d9fe71956a0677133a3e9a6e03bc39633de936a9588260b6ea/hypothesis-6.156.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e226328d39d991d6ec73dc44e717dfa8ec0ed86cb3870995e6ae76b5fec5c157", size = 1069705, upload-time = "2026-07-10T14:21:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/77/09/b0a6ef4124e142ad7d5459840247e5434f85391d0f2431ebdfef265bbadb/hypothesis-6.156.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:750003b04031c9012a0571c14e97ef70111ed42bfbcefab91ba4b619474e32dd", size = 1120948, upload-time = "2026-07-10T14:22:08.406Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/0b963f188e05c99e72cfb4744500e8124c1fc6a1a97152bc1a09b2a6ab22/hypothesis-6.156.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:012358f9d31fc5b0fadd092fa285d91690a1c6622e75f7120aaec4fd1b12926f", size = 1243853, upload-time = "2026-07-10T14:21:26.89Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e4/62b7c0c45ae9aaf181dc3a355cf042db648141c328b54a40ceae5f5dd2f5/hypothesis-6.156.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:29318faf72ce169b03e90a9e1a8167b2bddb667a653ef66a2f90427b13a191ea", size = 1287779, upload-time = "2026-07-10T14:21:51.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/90/c9fccb68c072cff3b15695e79f54376df382ae986c2c92d296eef1493306/hypothesis-6.156.5-cp312-cp312-win_amd64.whl", hash = "sha256:3d3a71d67f5309f110855083644b8a0f032adda1c2672b08104470c05a6aac5e", size = 637645, upload-time = "2026-07-10T14:22:26.601Z" }, + { url = "https://files.pythonhosted.org/packages/18/f4/5a6f9f99028a4fb7c9c08e37360193eea174875b05055636dcc6ba63aa62/hypothesis-6.156.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6bc493e3bcb815d1dfe29e0c169f1032c42eea9fd34c7cef734e744d93a17476", size = 749235, upload-time = "2026-07-10T14:22:24.956Z" }, + { url = "https://files.pythonhosted.org/packages/10/e7/141271fe6e4a00825f157e892208a7f4562b62175ffcd0818f813e1135e8/hypothesis-6.156.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5332cbc4bbbc1b4495f534df8c8a7b4fc50ca6a08650b1250896a36c091b0cb2", size = 742068, upload-time = "2026-07-10T14:21:40.954Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6c/62dc31e337c9150d75cec7da7192606711417909175b7d399d52b201d53b/hypothesis-6.156.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8aa21d73ce8238f438eca30e09ab3b7efef37c60c4da21415903d0e45fbffd5", size = 1069905, upload-time = "2026-07-10T14:21:45.399Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c8/9231c8646a5387a80c53f634a22cd5727f7eeb211251b45ef2a3bf0ffa37/hypothesis-6.156.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7123442269105b66924ebcabca92705415db1dd968404f730afd93552840982", size = 1121169, upload-time = "2026-07-10T14:21:30.598Z" }, + { url = "https://files.pythonhosted.org/packages/71/95/a282073b68052868e2ea72b3aef061ab2fd21917f02b31765dbaafa93836/hypothesis-6.156.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8d93f58fa5b77647e5e3b620e2a6d43682adc64a51d4ce7790f0d9ef4c9a9606", size = 1244127, upload-time = "2026-07-10T14:21:20.049Z" }, + { url = "https://files.pythonhosted.org/packages/fe/35/05aaad619f8a50f9d51e9d1b2796787551776df722b247f03c265e101736/hypothesis-6.156.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6b39871997ceefceb4a719eb374a3007aa2768847fa250d987acb2384be8ff5c", size = 1288044, upload-time = "2026-07-10T14:21:53.896Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9d/fc8a3b3c959e0e37462e3d68d2474948b5c7a9439f2e4fbf9583a468b7e2/hypothesis-6.156.5-cp313-cp313-win_amd64.whl", hash = "sha256:a724336107450452e8fa649a3c8f24e2dc01bb7a1f12c475a9f4545f2dc0889c", size = 637919, upload-time = "2026-07-10T14:21:25.423Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/ba02f0c8257ed8950487dea86fcc07c44bf5f19b81ab105b58f8f10d6833/hypothesis-6.156.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0f3eccde4d2de4d0d8343076da8ee694e6fc8a9970e00c751328cc841a90500b", size = 749285, upload-time = "2026-07-10T14:21:21.278Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a0/042091e7a657dfac98fbc8c3c639a35fd387d5d4b4c038bdfdb43af21584/hypothesis-6.156.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c79afa29ef2ef0e96f147ef408ef0d8bbf7a19f218b373913422ac746494ad8e", size = 742288, upload-time = "2026-07-10T14:21:06.373Z" }, + { url = "https://files.pythonhosted.org/packages/99/1b/db9a69bea7fe6684925d27cfe788095a4f0258ef445d95d72dd72522b836/hypothesis-6.156.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8ac71d1b07b692d7e1930d62e1232754f8e7f6507e404f4bcbe16e28a108a47", size = 1070075, upload-time = "2026-07-10T14:21:38.192Z" }, + { url = "https://files.pythonhosted.org/packages/6d/12/d5d5ecaf552ac033d8928d48b5aa042b9e3404a0095830ed61e06402b270/hypothesis-6.156.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff748c5746af6b83b0e5ad35eff0b99e75d021bce57556813062a34e757be48f", size = 1121494, upload-time = "2026-07-10T14:21:10.47Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/de2256681ff4dabefbdfaed9f0a951810c35c030d5d5fcdf3424a3e5828f/hypothesis-6.156.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f08d560ecabd4bff4a7f4b34bbc655cdc83b317578c7265e8da0d10b66e272e", size = 1244646, upload-time = "2026-07-10T14:22:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/cf/63/47c7ff0710c03fe321505db83939451962eac87d1d0ce5e5c003ec74b30c/hypothesis-6.156.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ab0598cd43f0e62ca8a58ffde04efd0783fa5795b099959717f9581fe8753355", size = 1288481, upload-time = "2026-07-10T14:22:01.212Z" }, + { url = "https://files.pythonhosted.org/packages/73/91/590491e00ac43940dc42a6845f283cf27bb983f87bb4798f07673eaeef11/hypothesis-6.156.5-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:6f797675b82b7417961dd0d634fe6592d5c7e0e564596a001ec6254a100dbc35", size = 586424, upload-time = "2026-07-10T14:21:46.823Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1e/a2227c775f874c28efc8f12b05823ec3dac81ea8ad721a6afe609a1929f3/hypothesis-6.156.5-cp314-cp314-win_amd64.whl", hash = "sha256:39397347a8b524ea68f2a25dd4178edffe0165ba1cb93190c8ea696070e7b5e7", size = 637739, upload-time = "2026-07-10T14:21:04.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d7/0ec1deefcf431285ea00e1e86e8af044f08dc1e9d268415ea2707a1b5bca/hypothesis-6.156.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e8f5093fcaa36e3a5a750b242c41005bf8800f144372dacdcf4c1c4f7ed54e24", size = 747398, upload-time = "2026-07-10T14:21:55.251Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d9/0928982874950fbc276648eba371bea9590af6cfe902bd1adf84bb9a9a04/hypothesis-6.156.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:676cbfb1ff9ad8fa0e10e63ef7e1aec8f1f15c44772375eea6708f988992adaf", size = 740858, upload-time = "2026-07-10T14:22:02.793Z" }, + { url = "https://files.pythonhosted.org/packages/09/57/eb180b67835071e39fbc1d59455683abe9b3bdba3c682be7de739959d02b/hypothesis-6.156.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e34195c4233707fa23abdfc6f1234b6b62d2579d395ed96a56f87e5064226", size = 1069265, upload-time = "2026-07-10T14:21:31.919Z" }, + { url = "https://files.pythonhosted.org/packages/6b/31/6b4e1419fe0e0d1b266b79c48c0e18bc261d43491d46f4dd44594c1f2c42/hypothesis-6.156.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad53de9310c83bbf0c20901d41421eaf138609314d7421f72db39d39a7f0cf7", size = 1120372, upload-time = "2026-07-10T14:22:20.036Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f8/645ff174828e79183a09f465b29e76331a6f408a00fe6dea3763460353ed/hypothesis-6.156.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5959642a2ba80156baa6e23cc64d40d27e0d160c019c0aae2d2de62a3537963b", size = 1243138, upload-time = "2026-07-10T14:22:16.792Z" }, + { url = "https://files.pythonhosted.org/packages/3f/59/611cfaa2b474d4fe69064ed5a1b7ec4698845755fbe80488ff7cb62efb6b/hypothesis-6.156.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d8ea564a59f3f163f36b7c3522c6bd05c82a508acca5ac6bc5e1b3c265bb8814", size = 1287257, upload-time = "2026-07-10T14:21:08.948Z" }, + { url = "https://files.pythonhosted.org/packages/14/80/0c0a5a6381350b109fd4a696babe90c637a55f5710281059259079cc13b6/hypothesis-6.156.5-cp314-cp314t-win_amd64.whl", hash = "sha256:f1aaebfa3d7c888ad905a6acc933d3093a227b97a136d27ea6d47a531f8bc110", size = 637830, upload-time = "2026-07-10T14:21:29.324Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ad/b034888717705c26860ff808c98298a41540aef0bd25dc395614334e74ea/hypothesis-6.156.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:47a23e44e900cc44f764483158095a138a1e5b4dd5abcd3e2b54ddf56bcc7ba3", size = 749517, upload-time = "2026-07-10T14:21:07.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6a/6239e3918cbb48f2fb86420c31b210dde24d692b51da29328a3bafdc0067/hypothesis-6.156.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:268b5df24a92627daa04f241f1fc01331076158927debca953d19a1ac543f3d9", size = 744372, upload-time = "2026-07-10T14:21:18.908Z" }, + { url = "https://files.pythonhosted.org/packages/f3/99/9dcac9f5058e0d0ef5e5372760c7d994c4b739d461bfb24c6554e9ee3b13/hypothesis-6.156.5-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e09354067493aa7a797aacb980a1dc6e063c53cae36d9db8a20bd030d3df435f", size = 1070856, upload-time = "2026-07-10T14:21:39.55Z" }, + { url = "https://files.pythonhosted.org/packages/15/e9/63280b1d04ade37a1be922b6817266b75ecc68a32b6988d6e928415760fe/hypothesis-6.156.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a50062d059fe9f34baaf80aa31c4ef4f42d42fac2a39702d89546a04337a9a7", size = 1122589, upload-time = "2026-07-10T14:21:35.241Z" }, + { url = "https://files.pythonhosted.org/packages/88/6c/6ec6cba54d072b3a7e0ef248a18364d144638115cb95e4681ec9a5cd1290/hypothesis-6.156.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:48173754d2e749649bdd7b978262ef4e597e697a66840e8e168fc0cc20e80ec6", size = 640773, upload-time = "2026-07-10T14:22:13.317Z" }, ] [[package]] @@ -1863,75 +1913,75 @@ wheels = [ [[package]] name = "msgpack" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/23/6139781ca7aadf656fa8e384fa84693ffb13f299e6931b6526427fe5e297/msgpack-1.2.0.tar.gz", hash = "sha256:8e17af38197bf58e7e819041678f6178f4491493f5b8c8580414f40f7c2c3c41", size = 183017, upload-time = "2026-06-11T04:16:10.775Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/52/fed22bca455ff3ed28c0ee0d1117398b7cb3ce440270050e85b09240fa8d/msgpack-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ed8c9495a0f12d17a2b4b69e23f895b88f26aabe40911c86594d3fbddecfff08", size = 82473, upload-time = "2026-06-11T04:14:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/3b/09/0b54d386024a9fa2073135212c11d1e83b059d98459d943d5a82ba9dcdc9/msgpack-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7384859c90b45a28a4b31aa50b49cca84504c9f27df459cea6e072627650dcb", size = 82150, upload-time = "2026-06-11T04:14:39.985Z" }, - { url = "https://files.pythonhosted.org/packages/44/ba/c6310a6f37e9bf9279b492640ec425e6f6e68a94e4cac4782ab518b05d64/msgpack-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63b35e8e65f04ff7ad5c9c70885da587c74f51e4b4eb3db624eac6d250e8cf59", size = 398355, upload-time = "2026-06-11T04:14:41.493Z" }, - { url = "https://files.pythonhosted.org/packages/d8/1b/f4bad0e9dea608b14d36065c44e347e4b10c0392f92cca441496cc0598ef/msgpack-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004c5a02acd3eca4e15e1ae7b461c32e3711105a28b1ad78be2f6facff4c523", size = 405162, upload-time = "2026-06-11T04:14:42.957Z" }, - { url = "https://files.pythonhosted.org/packages/63/34/4653bc7f426bd6ce9803f75133aa362232639e5adb8c6b99550107c71ed5/msgpack-1.2.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e2032dacb0a973fcbf7bd088415a369dae31c5af40e199d234806be22e86765", size = 372720, upload-time = "2026-06-11T04:14:44.532Z" }, - { url = "https://files.pythonhosted.org/packages/13/3c/8c607e10db2225af52107ffa918280483248363819fecb4437a35a1f4ae2/msgpack-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1feb100651fbe4b39826207cb20af065dfbfbfa43b1bafd7eaa2252abf7acfd", size = 390946, upload-time = "2026-06-11T04:14:46.054Z" }, - { url = "https://files.pythonhosted.org/packages/96/05/c4cb5fb30569cff4b4c7be4574adddb0faf7faaf3049bbab000b6f07da5b/msgpack-1.2.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:82487709d4c597d252311a65370220675fb1cc859e7da9269a3060c03ac02cf6", size = 374062, upload-time = "2026-06-11T04:14:47.817Z" }, - { url = "https://files.pythonhosted.org/packages/40/d7/b51b11e58277e6b678ba5a2f6608f88fdb0778973391a39d7f1a385f5bde/msgpack-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0268c67a74f5f913f545a0fdbbfaa3f6ebcf23b4c3209bb99704a2ea87e13f90", size = 405458, upload-time = "2026-06-11T04:14:49.618Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/9eca2961be302a6fc77a3fcb15faec749e325c9f0a8fe9c4c4576fc2cad5/msgpack-1.2.0-cp310-cp310-win32.whl", hash = "sha256:7df87173b0e13ddd134919731f13525dbbf75204145597decf1cb86887ebb492", size = 64010, upload-time = "2026-06-11T04:14:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e3/55b14ae13ed056ed35364ff71144c6a12af25227c20093045a945d08273a/msgpack-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6371edb47788fbfd8a22016f9a97b5616dd9849bc50abcbb8e82d38f71efa096", size = 69863, upload-time = "2026-06-11T04:14:52.376Z" }, - { url = "https://files.pythonhosted.org/packages/ee/23/35de3182a647fcc84ab304160169edfa5dac7bbd8913fbed0a505ddc0d55/msgpack-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec35cd3f127f50806aa10c3f74bf27b749f13ddf1d2217964ada8f38042d1653", size = 82368, upload-time = "2026-06-11T04:14:53.57Z" }, - { url = "https://files.pythonhosted.org/packages/aa/79/8d9bfdab933b1c7a02aba9518605a81aa30d38e9efd4915ec1a6b2d55778/msgpack-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:317eb298297121bfad9173d748124a04a36af27b6ac39c2bbc1db1ce57608dcf", size = 82095, upload-time = "2026-06-11T04:14:54.784Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e1/b5accbc1354edbcee107fb35ec247db0547e91c3f90e4fabdeaee500a5a6/msgpack-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50fe6434de89073273026dd032a62e8b63f8857a261d7a2df5b07c9e72f3a8f7", size = 413818, upload-time = "2026-06-11T04:14:56.1Z" }, - { url = "https://files.pythonhosted.org/packages/82/31/1141cbbf7118d525834f20dcd614d1b85f1f2ffd33bc2a5ce710e6dd2516/msgpack-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106c6d333ff3d4eda075b7d4b9695d1752c5bcc635e40d0dbaf4e276c9ed80e1", size = 423790, upload-time = "2026-06-11T04:14:57.509Z" }, - { url = "https://files.pythonhosted.org/packages/04/e7/9582f2bd4d7546139fe297740de49bd1f7ef2d195eb0bb9fa5efeee88158/msgpack-1.2.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:67055a611e871cb1bd0acb732f2e9f64ca8155ca0bba1d0a5bb362e7209e5541", size = 387521, upload-time = "2026-06-11T04:14:59.08Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/5aadd08ff068bfd42e2ac0be6a20aa9819965df8622e87c1f0c6119c1c22/msgpack-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ceec7f8e633d5a4b4a32b0416bef90ee3cd1017ea36247f705e523072e576119", size = 406324, upload-time = "2026-06-11T04:15:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/39/ee/3041564f0cc4c2fe7c53315aec0edf3d84807fc9b9ea714e6ac07dbdb1db/msgpack-1.2.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7ec5851160a3c2c0f77d68ddec620318cd8e7d88d94f9c058190e8ce0dfa1d31", size = 384242, upload-time = "2026-06-11T04:15:02.121Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d4/de94b3dbc266229f4c2ce84485eeb221220351b7f1931029e875995bb232/msgpack-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd7140f7b09dbe1984a0dff3189375d840247e3e4cf4ac45c5a499b3b599c8d2", size = 420392, upload-time = "2026-06-11T04:15:03.692Z" }, - { url = "https://files.pythonhosted.org/packages/f7/5d/c4a3fde69a292eecb202caaa87c29df7728644a65118614b821bcaddc05a/msgpack-1.2.0-cp311-cp311-win32.whl", hash = "sha256:cbfd54018d386da0951c7a2be13de0f58559d251313e613b2155e52ed1cbd8f1", size = 63976, upload-time = "2026-06-11T04:15:05.355Z" }, - { url = "https://files.pythonhosted.org/packages/18/fa/df47f83115375e7717c985265a30f3ba096c5331518e28fb647b55c46d31/msgpack-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:653373c4614c31463ba486a67776e4bb396af289921bd5353e209534b71467fa", size = 70273, upload-time = "2026-06-11T04:15:06.529Z" }, - { url = "https://files.pythonhosted.org/packages/54/d1/ffd02e54c064aa73b6b53aa08171f92dc406727077ff275d7050c6aca28a/msgpack-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:7a260aea1e5e7d6c7f1d9284c7360d29021627b61dc4dd7df144b81210810537", size = 64783, upload-time = "2026-06-11T04:15:07.677Z" }, - { url = "https://files.pythonhosted.org/packages/44/07/dcb13f37e670257c8d0e944f116c799c34ac6968ecb48c83619f7e91d8b5/msgpack-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2d6047ccd11a12c96a69f2bfe026471abef67334c3d0494a93e5310e45140a2", size = 82888, upload-time = "2026-06-11T04:15:08.992Z" }, - { url = "https://files.pythonhosted.org/packages/84/5f/6643b2a6a36ca4bc73c7674831be1d4d581cceecc7eb019dba1915951739/msgpack-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0347e3ac0dfee99086d3b68fe959da3f5f657c0019ddbaeaaa259a85f8603422", size = 82223, upload-time = "2026-06-11T04:15:10.182Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c8/9e1668b9897358e5ab39a18142e38be3cf15807e643757782da9f4a53cb3/msgpack-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25552ff1f2ff3dc8333e27eabb94f702da5929ed0e07969688194a3e9f12e151", size = 409700, upload-time = "2026-06-11T04:15:11.441Z" }, - { url = "https://files.pythonhosted.org/packages/38/ed/b7728573156d70b6b094233b0f38d876fc37340826cf852347ec2c7ca8ca/msgpack-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0d94420d9d52c56568159a69200af7e45eadb29615fa9d09fada140de1c38c7", size = 420090, upload-time = "2026-06-11T04:15:12.868Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f7/5ea755a89868c04f9cdf6d96d2d99da4b3d198af10e76a6082dd0fceccc0/msgpack-1.2.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d16e1f2db4a9eebc07b7cc91898d71e710f2eed8358711a605fee802caff8923", size = 378538, upload-time = "2026-06-11T04:15:14.511Z" }, - { url = "https://files.pythonhosted.org/packages/80/2d/126e59332a439c94ffd682c38ca0102b23480e2784b3dac48d8959b0bbac/msgpack-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9cb2e700e85f1e27bbb5c9de6cc1c9a4bc5ac64d5404bdcbcb37a0dc7a947a3", size = 399468, upload-time = "2026-06-11T04:15:16.133Z" }, - { url = "https://files.pythonhosted.org/packages/da/f9/7abcef683a0ad2e5ab3a4940344aad9f20cdf1f42057ecb0982cf55085d6/msgpack-1.2.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:717d0b166dd176a5f786aeafff081f6439680acf5af193eb63e6266c12b04d3d", size = 374212, upload-time = "2026-06-11T04:15:17.536Z" }, - { url = "https://files.pythonhosted.org/packages/27/23/2d62cf0e971678e96f8a3cfa9bd77fb719ddb98da73790f63c53fd847ad8/msgpack-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e87c7a21654d18111eb1a89bd5c42baba42e61887365d9e89585e112b4203f9e", size = 414361, upload-time = "2026-06-11T04:15:18.99Z" }, - { url = "https://files.pythonhosted.org/packages/32/fb/f5c153f614037aaf802d291a4653ba1bb731f56feacba886f7c21c109e56/msgpack-1.2.0-cp312-cp312-win32.whl", hash = "sha256:967e0c891f5f23ab65762f2e5dc95922759c79f1ef99ef4c7e1fdd863e0d0af9", size = 64389, upload-time = "2026-06-11T04:15:20.237Z" }, - { url = "https://files.pythonhosted.org/packages/90/af/8aafce6e5544b43b84cb670aca40c8bea7eb5ae8f42bfcbdc7098739987a/msgpack-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:6c23e33cee28dcffa112ae205661da4636fd7b06bd9ad1559a890623b92d060b", size = 71185, upload-time = "2026-06-11T04:15:21.51Z" }, - { url = "https://files.pythonhosted.org/packages/ba/08/9cc94be1fc1fe3d1379d439326259aef0344274f64623a8138feb54dff68/msgpack-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:6eeb771571f63f68045433b1a35c0256b946f31ed62f006997e40b8ad8b735af", size = 64481, upload-time = "2026-06-11T04:15:22.639Z" }, - { url = "https://files.pythonhosted.org/packages/7d/26/2902c6946ab5c8fe1e46e40842dfc32b8824464ad5cd4725364fd83f7a58/msgpack-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a1d30df1f302f2b7a7404afbac2ab76d510036c34cf34dffb01f704a7288e45", size = 82621, upload-time = "2026-06-11T04:15:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/c9/59/7e6b812629d2f919e586041bffc130e1af32079f71bb20699eed54ed6d92/msgpack-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:581e317112260d8ca488d490cad9290a5682276f309c41c7de237a85ed8799c8", size = 81866, upload-time = "2026-06-11T04:15:25.032Z" }, - { url = "https://files.pythonhosted.org/packages/31/13/8c291196e60aafdbae38f482205d79432297749ac5d412fe638154fb6f1d/msgpack-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6827d12eacc16873eba62408a1b7bbe8ecfb4a8f7ed78a631ae9bae6ad43cf2", size = 405618, upload-time = "2026-06-11T04:15:26.235Z" }, - { url = "https://files.pythonhosted.org/packages/fb/63/68f5d0ea81e167db5f59ddb94dc6f837667062113feff1c73fabf8907061/msgpack-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a186027e4279efa4c8bf06ce30605498d7d0d3af0fba0b9799dce85a3fd4a93c", size = 416468, upload-time = "2026-06-11T04:15:27.732Z" }, - { url = "https://files.pythonhosted.org/packages/73/58/567dddf5c5a2790f673bcd7d80c83466d68e5ee9a9674ebca3db8101c0c8/msgpack-1.2.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a96142c14a11cf1a509e8b9aaf72858a3b742b7613e095ce646913e88ce7bd99", size = 374464, upload-time = "2026-06-11T04:15:29.286Z" }, - { url = "https://files.pythonhosted.org/packages/0d/30/0c2342fc9092e4498045f5f60bca6ccbe4f4d87789778c2300e6fd6efe82/msgpack-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50c220579b68a6085b95408b2eaa486b259520f55d8e363ddc9b5d7ba5a6ac6d", size = 395879, upload-time = "2026-06-11T04:15:30.973Z" }, - { url = "https://files.pythonhosted.org/packages/b9/11/9565b29b58ce3c33e177b490478b7aaeb8f726ecaaeda26d815893c1db5a/msgpack-1.2.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4dcb9d12ab100ecacdfaaf37a3d72fe8392eacc7054afc1916b12d1b747c8446", size = 371749, upload-time = "2026-06-11T04:15:32.418Z" }, - { url = "https://files.pythonhosted.org/packages/f2/da/7bade19d60b73e2ef73fb76aaf4504c112a70cb760951b7202a0c64b5111/msgpack-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a804727188ab0ebb237fadb303b743f04925a69d8c3247292d1e33e679767c15", size = 410416, upload-time = "2026-06-11T04:15:34.053Z" }, - { url = "https://files.pythonhosted.org/packages/6d/14/c0c619571c02432208a5977a8dbdd3fc65fe1369f8226ca4b6d08cca87d8/msgpack-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1a1ac6ae1fe23298f79380e7b144c8a454e5d05616b0096584f353ba2d750114", size = 64357, upload-time = "2026-06-11T04:15:35.535Z" }, - { url = "https://files.pythonhosted.org/packages/50/a5/de06718460909aa965737fec4cfe8a15dedc6544a8c55feeb6956fa0d6e3/msgpack-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c3c80949d79578f9dc85fd9fb91edfe6694e8a729cd5744634d59d8455fdde3", size = 71057, upload-time = "2026-06-11T04:15:36.83Z" }, - { url = "https://files.pythonhosted.org/packages/c7/52/73446b0141c94a856e22b787c56709c0815fc34f185326577e15b26d8cfe/msgpack-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fcf8f76fa587c2395fd0057c7232dbf071241f9ad280b235adb7ab585289989e", size = 64490, upload-time = "2026-06-11T04:15:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/35/3d/a7e3cdafa8c0cf36c81e2fa848ec4d30cf089459af45b390ad03f9ce6f49/msgpack-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f854fa1a8b55d75d82ef9a905d9cdbeffdf7897c088f6020bd221867da5e56a5", size = 83032, upload-time = "2026-06-11T04:15:39.38Z" }, - { url = "https://files.pythonhosted.org/packages/ca/aa/53ddfba0e347cc4b484e95f629c5850b9e800ca8390c91ffc604407acf87/msgpack-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e90df581f80f53b372d5d9d9349078d729851a3a0d0bd74f53ccb598d01e45b8", size = 82600, upload-time = "2026-06-11T04:15:40.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/fd/e64c2c776e6dbad0af3c963fe0c0dd1ee1ba09efac478b233ab1db41868f/msgpack-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b276ed50d8ac75d1f134a433ae79af8557d0fa25ee5b4737da533dfc2ce382e8", size = 404342, upload-time = "2026-06-11T04:15:41.87Z" }, - { url = "https://files.pythonhosted.org/packages/1b/60/fb9a08e6ccba882dfd370a5837fe3a07572938fdfe954f0f17fdf3e574b9/msgpack-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:544d972459c92aa32e63b800d07c2d9cf2734a3be29cee3a0b478a622850e9f5", size = 412351, upload-time = "2026-06-11T04:15:43.253Z" }, - { url = "https://files.pythonhosted.org/packages/37/4d/df5c575c274fedc68ac9c6c61d045161899efad2afcdc25138efa7edde69/msgpack-1.2.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a070147cc2cf6b8a891734e0f5c8fe8f70ed8739ab30ba140b058005a6e86af4", size = 373331, upload-time = "2026-06-11T04:15:44.754Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a4/c8b98f8191e985ed2003d87664ce3c95cca41db5d0cf6bf4f54327d32ec8/msgpack-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7685e23b0f51745a751629c31713fbefdef8896b31b2bb38299dfa4ae6c0740c", size = 394654, upload-time = "2026-06-11T04:15:46.423Z" }, - { url = "https://files.pythonhosted.org/packages/d4/49/76f036720a602ea24428cfec5ec806f2487c0380b1bff0a2aa3094e15f87/msgpack-1.2.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b9204daeee8d91a7ae5acf2d2a8e3983be9a3025f38aa21bfaefbd7eea84a7dc", size = 370624, upload-time = "2026-06-11T04:15:48.062Z" }, - { url = "https://files.pythonhosted.org/packages/9f/38/40af3d29232833705a43b0fce0d07425cc280a7b92ab2b29932425b40df4/msgpack-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bfc057248609742ebbabf6bcd27fea4fd99c4980584e613c168c9b002318298f", size = 408038, upload-time = "2026-06-11T04:15:49.669Z" }, - { url = "https://files.pythonhosted.org/packages/30/b2/f140ca450524dff4d8d0eb81eb9ed75f8f3e0b1f12e49c5b01617cfa0b1c/msgpack-1.2.0-cp314-cp314-win32.whl", hash = "sha256:a3faa7edf2388337ae849239878e92f0298b4dab4488e4f1834062f9d0c410c9", size = 65823, upload-time = "2026-06-11T04:15:51.062Z" }, - { url = "https://files.pythonhosted.org/packages/4d/13/6517bf966b841c7675ded30701a068ce141f3e698a27aaa35c702d8e078b/msgpack-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:1a3effc392a57744e4681e55d05f97d5ee7b598747d718340a9b4b8a970c40e1", size = 72484, upload-time = "2026-06-11T04:15:52.289Z" }, - { url = "https://files.pythonhosted.org/packages/45/8c/1d948420fdaa24de4efdb8012a6a5bebe09c82ee002b8c2ca745e9917f1f/msgpack-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:56a318f7df6bec7b40928d6b0519961f20a510d8baabf6baa393a70444588f0a", size = 66657, upload-time = "2026-06-11T04:15:53.583Z" }, - { url = "https://files.pythonhosted.org/packages/39/16/1674faa1b7bddc19e79b465fd8e88e2cf4e3f7cae90723740701e8541068/msgpack-1.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:afa4a65ab2097795e771a74a3a81ea49534aaeba874eaf426a3332268e045ae6", size = 86093, upload-time = "2026-06-11T04:15:54.98Z" }, - { url = "https://files.pythonhosted.org/packages/dd/24/f241bcfdd9e96b2246289357c5a5e5a496189fd41c5844bee802c116aac7/msgpack-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:409550770632bb28daa70a11d0ed5763f7db38f40b06f7db9f11dd2794d01102", size = 86372, upload-time = "2026-06-11T04:15:56.381Z" }, - { url = "https://files.pythonhosted.org/packages/94/c9/57f8ab98a1b21808c27b6dd6029053e0a796ffbb9b371e460dbe997011a9/msgpack-1.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf47e3cd11ce044965a9736a322afdd390b31ed602d1c1b10211d1a841f1d587", size = 428207, upload-time = "2026-06-11T04:15:57.739Z" }, - { url = "https://files.pythonhosted.org/packages/17/6b/4fd4aa739f131ded751ca7167c8ee87d2aab32506ebbeea893b60b51d343/msgpack-1.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:204bc9f5d6e59c1718c0a4a84fc8ff71b5b4562faac257c1a68bca611ecf9b72", size = 426082, upload-time = "2026-06-11T04:15:59.356Z" }, - { url = "https://files.pythonhosted.org/packages/f9/00/db88e9a08fcd6513decaad06cbd5c168142bc3e662fb2f1aca3a563b7aa1/msgpack-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:610154307b27267266368bc1d1c7bb8aeb71da7be9356d403cb2442d9e6399f5", size = 378355, upload-time = "2026-06-11T04:16:00.916Z" }, - { url = "https://files.pythonhosted.org/packages/54/84/eee4dd703d7a600cf46159d621c070b0b9468cf3dbade4ea8272bf5232a4/msgpack-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6799f157bb63e79f11e2e590cfdb28423fc18dd60c270c3914b5b4586ae36f7e", size = 410848, upload-time = "2026-06-11T04:16:02.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/0a/195e2c549fd4631eb7f157d016ff15a10c4c1cf82b6d0a9b1edaef5174b1/msgpack-1.2.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:72bd844902cf0a5ac3af2ef742f253cd0b1e5bcd184f49b4fb9a6a1f7bf305e8", size = 376152, upload-time = "2026-06-11T04:16:04.041Z" }, - { url = "https://files.pythonhosted.org/packages/45/9b/bdd143fa79baec411dc658f5686fed680a18b36fcea5fccb6af1b8c7d832/msgpack-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3c0bd450f78d0d81722c80da6cdbf674a856967870a9db2f6c4debc4d8b3c67c", size = 417061, upload-time = "2026-06-11T04:16:05.63Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ce/011ffcd8b919f55196ec53f12ae162e21c879d95afba226894314ff62c07/msgpack-1.2.0-cp314-cp314t-win32.whl", hash = "sha256:378caf74c4c718dfc17590ce68a6d710ed398ff6fcf08237de23b77755730b55", size = 70782, upload-time = "2026-06-11T04:16:07.105Z" }, - { url = "https://files.pythonhosted.org/packages/57/a8/9b8791ca96b1be6b9f659c718271e2cb7f99f73f58aad2dd0b30f750f6c0/msgpack-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:553b42598165c4dd3235994fd6e4b0dfb1ce5f3fd33d94ba9609442643015f38", size = 77899, upload-time = "2026-06-11T04:16:08.353Z" }, - { url = "https://files.pythonhosted.org/packages/5b/04/3fa2dffb87bf598696b86bde7cd642d0a7590520c3fa24cd19611dfebeb7/msgpack-1.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2825bb1da548d214ab8a810906b7dd69a10f3838b615a2cc46e5172d3cb44f6e", size = 71004, upload-time = "2026-06-11T04:16:09.556Z" }, +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/16/f70100614b69feb3ade7285f08c9c52d6cda0a5c03f3f5e2facd63acb211/msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c", size = 82926, upload-time = "2026-06-18T16:12:31.531Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3c/08ecd5cdfe4e2de43aec79062028ad0f7b2d9b1fea5430068c198ba570da/msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895", size = 82730, upload-time = "2026-06-18T16:12:32.894Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/a70c9cb1a04ecc134005149367dcfe35d167284e8f65035a1e4156ad17b5/msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203", size = 400729, upload-time = "2026-06-18T16:12:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7f/5ce020168cf0439041526e95aa068c722c016aee21624e331aeabeee2e8e/msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73", size = 407625, upload-time = "2026-06-18T16:12:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/fb7668ce0386819303047057aef6fc1da73b584291d9cff82b821744e2ef/msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833", size = 377891, upload-time = "2026-06-18T16:12:36.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dc/9ebe654a73c3aed2e40aa6b52e3c2a02b5f53ef0085fa235a45d5b367f87/msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8", size = 391987, upload-time = "2026-06-18T16:12:37.839Z" }, + { url = "https://files.pythonhosted.org/packages/42/eb/b67cf64218a2fa25e1c671fe1d3dbb06cbeb973e71bc4b822da079862d0b/msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7", size = 374603, upload-time = "2026-06-18T16:12:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2e/9ee200cde32fd1a0101b4006202fde554c1860adfb9bf7bff31ea4c08df8/msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce", size = 405121, upload-time = "2026-06-18T16:12:40.524Z" }, + { url = "https://files.pythonhosted.org/packages/43/b6/f10117be7ca7a51e8feed699a907b8e663a8cd66e115ae6b4fb30cc7945c/msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74", size = 64088, upload-time = "2026-06-18T16:12:41.762Z" }, + { url = "https://files.pythonhosted.org/packages/ba/93/89976c696fb0224662239d952c47b4d1661b34d79a332ef5584facaa8579/msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb", size = 70113, upload-time = "2026-06-18T16:12:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" }, + { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" }, + { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" }, + { url = "https://files.pythonhosted.org/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" }, + { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" }, + { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a", size = 419989, upload-time = "2026-06-18T16:13:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/85039b7b0eb168aaad7383a23c97e291a11f08351cb45a606ce865e4e3f1/msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6", size = 377544, upload-time = "2026-06-18T16:13:03.637Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/35963899493b32030c85fc513b723ae66144ac70c11ebc52e889e16e3d99/msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a", size = 400842, upload-time = "2026-06-18T16:13:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/8e2ac970c8f99264cd9997d1c73df5466bc19da3301d7dc5500862a9b089/msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1", size = 374108, upload-time = "2026-06-18T16:13:06.232Z" }, + { url = "https://files.pythonhosted.org/packages/17/dd/fa8bd265110dfa51c20cb529f9e6d240a16fafe7e645004c6af2d01353ba/msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64", size = 414939, upload-time = "2026-06-18T16:13:07.478Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8377a5ad8953fc0437c70cc98d9ae29f27fe5ac5109fbec0812085865735/msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac", size = 64504, upload-time = "2026-06-18T16:13:08.822Z" }, + { url = "https://files.pythonhosted.org/packages/57/7f/ce1e377df7e62461fefd9eb23bfb93a4a523f40a517b377b8f844d836828/msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24", size = 71421, upload-time = "2026-06-18T16:13:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/8f/32/ebfe84c9929f08f188d56c7a2fd913406a9ddad76a634697c1c43b8112e6/msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07", size = 64775, upload-time = "2026-06-18T16:13:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/dcddcab6f6c20ecb387ca5e980371cdb3f87ff69aeca388be97eebc4c074/msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064", size = 83151, upload-time = "2026-06-18T16:13:12.173Z" }, + { url = "https://files.pythonhosted.org/packages/64/71/fbcfa83a1d6a9c6091942d1cfd070962244664b87427a9a49a6897b1b219/msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056", size = 82351, upload-time = "2026-06-18T16:13:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/e3/10/ddf7b06db879e8792d13934ddda09ff20bd2a583fd84c9b59aae9b0e650b/msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc", size = 407518, upload-time = "2026-06-18T16:13:14.233Z" }, + { url = "https://files.pythonhosted.org/packages/79/d3/36a46a8ed992b781acbc05928bd5bee3c810cb0c3563bf81a7b0c04a1a76/msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d", size = 416405, upload-time = "2026-06-18T16:13:15.435Z" }, + { url = "https://files.pythonhosted.org/packages/f9/84/e8e9598b557c0ba6ddae901a73780a4c75ac667dddf59414b1e56a42fb34/msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155", size = 376257, upload-time = "2026-06-18T16:13:17.022Z" }, + { url = "https://files.pythonhosted.org/packages/40/16/738fe6d875ad7e2a9429c165322a4ec088f4f273cdfae63d96a89c467961/msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402", size = 397469, upload-time = "2026-06-18T16:13:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/ca/be/6d5952df75a7f24f35833af764c3a6860780364cb3a0030beb8099e1b2b4/msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c", size = 372802, upload-time = "2026-06-18T16:13:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/e1/39/e2ef7dbf0473bcb8dc7c50bf782a892d67414877b63e47fc88eb189ef5e6/msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6", size = 411273, upload-time = "2026-06-18T16:13:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c5/133f4512a56e983a93445c836c9d94d88f3bc2e0980ff4b9e577bd8416ce/msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707", size = 64471, upload-time = "2026-06-18T16:13:22.293Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/577e10b055096a7dd40732358cabaf7180a20c79ed1dcdbb618e4b9deac7/msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9", size = 71274, upload-time = "2026-06-18T16:13:23.455Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ee/0c0048e7cfbef23c6a94791b8959ab28155232e7956de8a305b5ff588f05/msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a", size = 64795, upload-time = "2026-06-18T16:13:24.687Z" }, + { url = "https://files.pythonhosted.org/packages/77/58/cce442852c6b9e1639c7c8ac8fd9143121cb32dab0f308df4d1426a8eb9c/msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d", size = 83610, upload-time = "2026-06-18T16:13:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/60/5c/15b4c7a0182f75ffa90751958ba36a9c01cafee367d49a3edc10ed140b01/msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7", size = 83138, upload-time = "2026-06-18T16:13:26.781Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/99e58722feaffc5f2fbcc0c8c0d1451ab9f84097f7af87291b46af2390f4/msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889", size = 406090, upload-time = "2026-06-18T16:13:28.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/03/8c63e8cf52958534ef688625965ab04c269a6cadd8caef16758b380a821a/msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720", size = 412106, upload-time = "2026-06-18T16:13:29.427Z" }, + { url = "https://files.pythonhosted.org/packages/63/d2/155d9e71b40e41fd934bc0c48b9b2770f22263e1ac20aad8e29fdca7be3f/msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190", size = 374851, upload-time = "2026-06-18T16:13:30.631Z" }, + { url = "https://files.pythonhosted.org/packages/98/48/deaf2326262a8d5ea3295ce9649912ecd3f551ba7ec8e33c665d2ba583f3/msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d", size = 396168, upload-time = "2026-06-18T16:13:31.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/2a/b4410f906c2ec0008f1608d3ab5143afc3ad3f4e6da0fed3ea2231d0bef4/msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24", size = 371959, upload-time = "2026-06-18T16:13:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/59/86/1edc67270099a528fa2093ea60fe191233cd238e4bd30cfacf7db79fc959/msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7", size = 408457, upload-time = "2026-06-18T16:13:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/82/90/8b630fef07d8c5ab457b71ff2c217910c83d333c7a68472c186e87cc504a/msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb", size = 65942, upload-time = "2026-06-18T16:13:36.056Z" }, + { url = "https://files.pythonhosted.org/packages/16/f1/467b81e98b24dd3885d7b1857728797b4ffc76a7a7483af4fb321a07de3c/msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b", size = 72627, upload-time = "2026-06-18T16:13:37.079Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/5d8c4c89985feb6acefb82a09e501c60392261856d2408d20bfe4f0360b1/msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7", size = 66908, upload-time = "2026-06-18T16:13:38.23Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ad2afb678b4de94496cd432b581759b756a92c1192d8c767edd6b132efdc/msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273", size = 86000, upload-time = "2026-06-18T16:13:39.44Z" }, + { url = "https://files.pythonhosted.org/packages/54/74/0b797484013128837f3b1cbb6cea019277c4de4e377dc512b4d9a0f92940/msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1", size = 86544, upload-time = "2026-06-18T16:13:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b4/b774d7eb95561739907fec675582f83203cf41c597a418c2589b4bfb8e9d/msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc", size = 427661, upload-time = "2026-06-18T16:13:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f9/3243191dc9937e00756c8bc1b0272fed8f23758e43df2a3b46f533e5090f/msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde", size = 426375, upload-time = "2026-06-18T16:13:42.936Z" }, + { url = "https://files.pythonhosted.org/packages/23/c7/1693111db9944ba4ad4b67a1e788400d78a0b6af7a6523dc7e4e58f8274b/msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4", size = 380495, upload-time = "2026-06-18T16:13:44.306Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2b/92f86956a0c13e8662f7e2ad630c4eb4db07497b967589bd5245e018b2c1/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d", size = 410897, upload-time = "2026-06-18T16:13:45.629Z" }, + { url = "https://files.pythonhosted.org/packages/da/ea/1479f72d200313a76fc2f823a79d1e07ed052ab7b8a0280640aa7b95de42/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355", size = 378519, upload-time = "2026-06-18T16:13:46.998Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4d/fa006060ffa1011d32bfae826fe766fe73e02982183601633b7121058ab3/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c", size = 419815, upload-time = "2026-06-18T16:13:48.205Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/aab6c946570496b78e67804721f3d5e2d62a93081b9b37df77764ef56347/msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1", size = 70914, upload-time = "2026-06-18T16:13:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/e608956488a2af014cfe6e3d665e090b8ee42aa14b07f8f95b8880d66b09/msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2", size = 77999, upload-time = "2026-06-18T16:13:50.467Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8a/27e2e57055176e366a46b85d02d68e7a5bcfbdd8474c9706375d965f24d3/msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107", size = 71160, upload-time = "2026-06-18T16:13:51.498Z" }, ] [[package]] @@ -2332,8 +2382,7 @@ version = "1.19.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.14.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", ] dependencies = [ @@ -2522,7 +2571,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.1.0" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, @@ -2533,9 +2582,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -2596,8 +2645,7 @@ source = { registry = "https://people.canonical.com/~lengau/python-apt-ubuntu-wh resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] @@ -2642,8 +2690,7 @@ source = { registry = "https://people.canonical.com/~lengau/python-apt-ubuntu-wh resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] @@ -2687,8 +2734,7 @@ source = { registry = "https://people.canonical.com/~lengau/python-apt-ubuntu-wh resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] @@ -2706,8 +2752,7 @@ source = { registry = "https://people.canonical.com/~lengau/python-apt-ubuntu-wh resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] @@ -2764,15 +2809,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.2" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/81/58c70036dffeccb7fe7d79d6260c69f7a28272bbd3909c29a01ea9422744/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3", size = 72212, upload-time = "2026-07-08T23:06:50.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ae/84bc0d2440c95772272bb6f4b3d09ccf08b2898fce89b3d4f969a9fc74e9/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe", size = 34181, upload-time = "2026-07-08T23:06:49.402Z" }, ] [[package]] @@ -2983,123 +3028,123 @@ wheels = [ [[package]] name = "regex" -version = "2026.5.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ed/0ad2c8edf634918eb4484365d3819fa7bd7f58daf807fe7fb21812c316e5/regex-2026.5.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a9e1328e17c84c1a5d22ec9f785ecef4a967fab9a42b6a8dc3bcbebd0a0c9e44", size = 489438, upload-time = "2026-05-09T23:11:29.374Z" }, - { url = "https://files.pythonhosted.org/packages/89/a9/4ed972ad263963b860b7c3e86e0e1bcc791def47b43b8c8efe57e710f139/regex-2026.5.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfe1ce50cbfb569d74e1e4337da6468961f31dbea55fd85aa5de59c0947a805a", size = 291270, upload-time = "2026-05-09T23:11:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/16/81/075930d9fa28c4ea1f53398dd015ee7c882f623539759113cda1257f4b82/regex-2026.5.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15ee42209947f4ca045412eae98416317238163618ace2a8e54f99586a466733", size = 289198, upload-time = "2026-05-09T23:11:35.769Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c8/5cdfbf0b5dc6599e1b6131eff43262e5275d4ec3469ce10216061659aadb/regex-2026.5.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb445ff3f725f59df8f6014edb547ee928ec7023a774f6a39a3f953038cbb2", size = 784765, upload-time = "2026-05-09T23:11:37.689Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ca/ae5fd6edc59b7f84b904b31d6ec39a860cbcecd10f64bd5a062ca83a4864/regex-2026.5.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:446ddd671e43ab535810c4b21cff7104945c701d4a14d1e6d1cd6f4e445a8bea", size = 852115, upload-time = "2026-05-09T23:11:39.973Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ce/a91cf555afb51f3b74a182e24ba073b91ea7bb64592fc4b315c111bb19fd/regex-2026.5.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b92817338591505f282cf3864c145244b1edcf5381d237038df955001091538", size = 899503, upload-time = "2026-05-09T23:11:42.48Z" }, - { url = "https://files.pythonhosted.org/packages/55/7f/725a0a2b245a4cf0c4bab29d0e97c74285d94136a65d1b55a6459a583502/regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b8a143aca6c39b446ea8092cde25cc8fe9304d4f5fecfbc1a9dbb0282703c2", size = 794093, upload-time = "2026-05-09T23:11:44.681Z" }, - { url = "https://files.pythonhosted.org/packages/e3/2a/996efbd59ce6b5d4a09e3af6180ceb62af171f4a9a6fb557d2f0ae0d462b/regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0f03aa6898aaaac4592479821df16e68e8d0e29e903e65d8f2dfb2f19028a989", size = 786234, upload-time = "2026-05-09T23:11:46.882Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/8731e8b8806174c9cdd5903f80a14990331c1f42fc4209b540952e9e010d/regex-2026.5.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ed457d8e98ae812ed7732bef7bf78de78e834eae0372a74e23ca90ef21d910f9", size = 769895, upload-time = "2026-05-09T23:11:49.324Z" }, - { url = "https://files.pythonhosted.org/packages/9a/0b/932473194bd563f342a412ae2ffbbd6da608306a2bc4e99249a41c2b0b92/regex-2026.5.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71b61c5bfe1c806332defc42ad6c780b3c55f661986d7f40283a3a88274b4c00", size = 774991, upload-time = "2026-05-09T23:11:51.261Z" }, - { url = "https://files.pythonhosted.org/packages/98/80/9523d196010031df25f7177ee0a467efbee436324038e5d99def17a57515/regex-2026.5.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3b1e39888c5e0c7d92cea4fc777396c4a90363b05de75d02eb459a4752200808", size = 848790, upload-time = "2026-05-09T23:11:53.232Z" }, - { url = "https://files.pythonhosted.org/packages/3c/07/56987b35e89edf47e4a38cf2845aeee476bfa688a6bdbd3e820cda461dc1/regex-2026.5.9-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6ba42b2e7e7f46cf68cc6a5ca36fa07959f9bbd9c6bdcc47b6ee76549a590248", size = 757679, upload-time = "2026-05-09T23:11:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/04/2a/ff713fff0c566507c06a4ce2dc0ae8e7eeebc88811a95fc81cf1e7d534dd/regex-2026.5.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c010eb8caca74bdb40c07498d7ece26b4428fd3f04aa8a72c9ac6f79e8faaac6", size = 837116, upload-time = "2026-05-09T23:11:57.934Z" }, - { url = "https://files.pythonhosted.org/packages/77/90/df6d982b03e3614785c6937ba51b57f6733d97d2ee1c9bc7531dbfab3a54/regex-2026.5.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a6a563446a41adc451393dc6b8e6ad87979efaee3c8738690a8d1b08ebead1b4", size = 782081, upload-time = "2026-05-09T23:11:59.607Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/4e88a5f7c3e98489aac4dd23142723d907b2a595b4a6abcbacabefeded09/regex-2026.5.9-cp310-cp310-win32.whl", hash = "sha256:954cc214c04663ee6d266fc61739cad83054683048de65c5bd1d640ad28098ac", size = 266247, upload-time = "2026-05-09T23:12:01.116Z" }, - { url = "https://files.pythonhosted.org/packages/6a/40/4b224cb0582b2dca1786726e6cdabe26abbf757d7f6718332f186da155d2/regex-2026.5.9-cp310-cp310-win_amd64.whl", hash = "sha256:b310768746dd314ea6e2ff4cc89ef215426813396ff4e94ee8e6f7096c8b6e03", size = 278416, upload-time = "2026-05-09T23:12:03.2Z" }, - { url = "https://files.pythonhosted.org/packages/12/4d/014fbe803204cab0947ee428f09f658a29632053dde1d3c6176bb4f0fd4c/regex-2026.5.9-cp310-cp310-win_arm64.whl", hash = "sha256:19c16ceb4a267a8789e25733e583983eeab9f0f8664e66b0bd1c5d21f14c2d4b", size = 270413, upload-time = "2026-05-09T23:12:04.649Z" }, - { url = "https://files.pythonhosted.org/packages/c2/dc/c1f2df4027e82fc54b5a473e4b250f5139faca49a0fbe29a48668d228f34/regex-2026.5.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48", size = 489445, upload-time = "2026-05-09T23:12:06.111Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/59f01110660081cce9c0bc30ebd0b5ee250dacf658e3248ed92f01e0e8ee/regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", size = 291271, upload-time = "2026-05-09T23:12:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/58/b6/14b2c84ff90ddb370c81d27503f4a0fcf071496416f4855f6cc8c5d81c35/regex-2026.5.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555", size = 289212, upload-time = "2026-05-09T23:12:09.266Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/4db86529117320de0c84afd90e70bb47434625875e34fcef9d8c127c5b16/regex-2026.5.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919", size = 792310, upload-time = "2026-05-09T23:12:11.416Z" }, - { url = "https://files.pythonhosted.org/packages/07/78/fe4800cd322f862ecffd2d553409b20d80650e5ed71b9d178f853d020b82/regex-2026.5.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451", size = 861721, upload-time = "2026-05-09T23:12:13.681Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d0/b3618a895dd8feb897c61bb2954edd265e1767d82a01d53065d5871127a3/regex-2026.5.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c", size = 906460, upload-time = "2026-05-09T23:12:15.443Z" }, - { url = "https://files.pythonhosted.org/packages/33/6f/1481597e859ef19508b345eec4afd1416ed6e6b459c75a64026ef193aecf/regex-2026.5.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc", size = 799843, upload-time = "2026-05-09T23:12:16.892Z" }, - { url = "https://files.pythonhosted.org/packages/73/59/955734c803f59108deccba3597ae440c76b62a652733c0006e6243758420/regex-2026.5.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d", size = 773610, upload-time = "2026-05-09T23:12:19.127Z" }, - { url = "https://files.pythonhosted.org/packages/68/8f/70c04a236d651c81881dac42ef8538bddda6121434509d0a22d9e601503b/regex-2026.5.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9", size = 781645, upload-time = "2026-05-09T23:12:20.806Z" }, - { url = "https://files.pythonhosted.org/packages/1d/96/05c7434d88185e5d27fe54aeb74df86bd77cd79f52f0b4eae54faa8fea70/regex-2026.5.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2", size = 854473, upload-time = "2026-05-09T23:12:22.465Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/6e3d8202d981f3117004bf341ee74893ba4ba8a9fbaf4b94615846550a08/regex-2026.5.9-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf", size = 763311, upload-time = "2026-05-09T23:12:24.351Z" }, - { url = "https://files.pythonhosted.org/packages/93/c7/e7737f1526b3fb32bd4c337fd6c71c3ebb5c8296fc34d11197e0955d2e35/regex-2026.5.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611", size = 844593, upload-time = "2026-05-09T23:12:26.341Z" }, - { url = "https://files.pythonhosted.org/packages/a5/27/0daffb1a535bb39f422c3d200f4ab023c71110ad66a32b366bee708baba0/regex-2026.5.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c", size = 789167, upload-time = "2026-05-09T23:12:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fc/294fe4fac4f2ed67207b17471815870c1c45b3a489e08e0ac96daea16ef6/regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", size = 266249, upload-time = "2026-05-09T23:12:30.141Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b0/8dce459f6245bcf8f6e9f23ac9569f1a0f15c131cc0745e82b43226204cf/regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", size = 278423, upload-time = "2026-05-09T23:12:31.676Z" }, - { url = "https://files.pythonhosted.org/packages/db/8d/f9aeff6ad63a3ef720386f2907e6d34a35a510a6e498ebad28b0fb3f6ab6/regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", size = 270420, upload-time = "2026-05-09T23:12:33.194Z" }, - { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, - { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, - { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, - { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, - { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, - { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, - { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, - { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, - { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, - { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, - { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, - { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, - { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, - { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, - { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, - { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, - { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, - { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, - { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, - { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, - { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, - { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, - { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, - { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, - { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, - { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, - { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, - { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, - { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, - { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, - { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, - { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, - { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, - { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, - { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, - { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, - { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, +version = "2026.6.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/05/e4f219230e11e774a6c9987d2ab0d0c6b8573e13a17e143d0015bee710ef/regex-2026.6.28.tar.gz", hash = "sha256:3cb4b6c5cb3060cc31efdc1fbb27c25fb9b29044afd87e40601a1c4d9db54342", size = 416101, upload-time = "2026-06-28T19:56:55.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/dc/f7a8c9cf0768f704153d358fae2bc883199bc4ea1e4aa458f1be9d0ef2ce/regex-2026.6.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b83932645630965fd860fdb70ebbf964bf3e8007f08851ea424d01f8d35454a8", size = 489471, upload-time = "2026-06-28T19:53:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/9786a4a2133e2f1cc5897ed3d2da3da29ff54b775ffa38bc5935fc24be82/regex-2026.6.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e81f1952355042e517dc9861ce65c676e4a098f42402993c40461786d1f794d4", size = 291294, upload-time = "2026-06-28T19:53:09.232Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1f/bfe5b529257f0853aa6b94146e0f6462f4d45aa4f3c05d5a828f415dfd40/regex-2026.6.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2097591101d70bcc108af64c46f6066bb698ee067fec5f75beac0be317639311", size = 289216, upload-time = "2026-06-28T19:53:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/25/56/f615165e90ac5f3b72b249240643439520bbac0ac60a9de06868528eba4c/regex-2026.6.28-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d7538a614b5842bf53ce329d07b43f97754ca7e6db8d69f347e071bce1c953", size = 784787, upload-time = "2026-06-28T19:53:12.393Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/c9e3ad31b3d5fbe1228fee8319e0c02a5460296624f220d08764547fe6ae/regex-2026.6.28-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5561e47bbe2b75373b695326507743fcdd4d2cc7f5022312024ccf39fa094e0", size = 852137, upload-time = "2026-06-28T19:53:14.287Z" }, + { url = "https://files.pythonhosted.org/packages/c0/77/d506a428e446466ee298f5425a774737d0671d070425ed794bb3314d60c6/regex-2026.6.28-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c10f2c5a55ab3dd8318d8ad5f11b530e2691c0edebebde7713066f484902c3fb", size = 899525, upload-time = "2026-06-28T19:53:15.987Z" }, + { url = "https://files.pythonhosted.org/packages/aa/72/becc00d839f19401f10a20168b44711c7b02f7f62bba875b2d8f98417435/regex-2026.6.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23f7e0cc60c72486b42a685f1ff4eec90d50d4fb05e4f9c7d5363b03aa02600d", size = 794116, upload-time = "2026-06-28T19:53:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/fa/11/ea2ca423eeaac2e18077a18b058614e9201f130750df2126d444e39acab2/regex-2026.6.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:76493755f79a88d5ed2c9e63a41d3c05997e0a7ffbe76ed8c4ded8be35b8b14c", size = 786257, upload-time = "2026-06-28T19:53:18.712Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9e/f5bf7ecbd14ff2086f015c54dc24fd0d74ba5327fef0de479213f8128615/regex-2026.6.28-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ff0f41a00f23ea5054acb61901380c41813d813eee3f80f800995710bcc52ecd", size = 769914, upload-time = "2026-06-28T19:53:20.564Z" }, + { url = "https://files.pythonhosted.org/packages/43/04/f9040a5360a06241ba5b7f2e6f1c6184e104a84e6f6522535700e94bf8e2/regex-2026.6.28-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c60b297292e7e1ef5d02a4759f9e452ee4c8bb95e168d8fd0b5db01bd806f9f", size = 775013, upload-time = "2026-06-28T19:53:22.067Z" }, + { url = "https://files.pythonhosted.org/packages/73/97/4e46f7abf2f864319d2bcac609af3c0532968c66a3364337778fd232b83c/regex-2026.6.28-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7cf03c87f7b9cbc25a8894cf9be83818406677b6b391b003ec7c884923387b5", size = 848814, upload-time = "2026-06-28T19:53:24.575Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b8/3d1f995727799a1e2e693e397acb7358094606e5591b6b5fd3128d2d1409/regex-2026.6.28-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:418208ea0af51cfed4f46eb9b1ea7cfc990ca284f0084ecbd951460fb089421e", size = 757702, upload-time = "2026-06-28T19:53:26.215Z" }, + { url = "https://files.pythonhosted.org/packages/20/10/fd5653b8572910a4fe9055f8959b070d7d9443c94ce986529fcdb5fb2a3c/regex-2026.6.28-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:7635fa2cddb917a6bbfac7890602573d2d8c4e470703b0640e6f86a988817ec3", size = 837140, upload-time = "2026-06-28T19:53:27.655Z" }, + { url = "https://files.pythonhosted.org/packages/5d/31/da77e3ef7b594a2aacbd03ce3d0050f33ab3e021df50c6901467c9006511/regex-2026.6.28-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bb96c13d6cf5880d31bbef84ca701a64d738aa491c2b79975cc33f8ad00a31e", size = 782105, upload-time = "2026-06-28T19:53:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4d/c379001448d0f58b6946f168d4af96ad60a16c1553259c27b0df8701b640/regex-2026.6.28-cp310-cp310-win32.whl", hash = "sha256:56f05194c4843957dd8b3af87eb0c52d8cf0509e7f18e172d727f5f8ff840646", size = 266728, upload-time = "2026-06-28T19:53:31.813Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/cb656529efa87d74cce0d69e606c745537016da3bdfae78f342af2242ee3/regex-2026.6.28-cp310-cp310-win_amd64.whl", hash = "sha256:70710927033af3b54369f17aaba1343b97a23d0b1aa994fa1512b08b1b8c136a", size = 277901, upload-time = "2026-06-28T19:53:33.293Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ac/d35ccc309c9409406445ab2ef0b56f6a341a916ccff49ff9ac5cc6bb8e9b/regex-2026.6.28-cp310-cp310-win_arm64.whl", hash = "sha256:ed7b30185ee3f8b9b053b0be567b4d226016e2afbebc17fde1c6a4580937b688", size = 276880, upload-time = "2026-06-28T19:53:35.029Z" }, + { url = "https://files.pythonhosted.org/packages/72/db/9051b36294bdbabaa9c7db57db0fbcdfbd17f7a106c539bb423d0323faea/regex-2026.6.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a71b51dd08b9b62f055fafab3dee8af8bd2ec81b373a44caef18d6c5ca28f43a", size = 489481, upload-time = "2026-06-28T19:53:36.684Z" }, + { url = "https://files.pythonhosted.org/packages/35/3f/24097a3c3ff30f9a639888900faaecabcf5f54a5bc9c851c297e11b349ef/regex-2026.6.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c26a47770d30a0f85c01e261d2a3ebc342c4af6fd666dbd8c1fe4cbf3adf726", size = 291292, upload-time = "2026-06-28T19:53:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cc/e0d762a189cfb4e8926d16e691720690d139a977b38fdb80230c259332ab/regex-2026.6.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5efbc1af38f97e300d43028e5a92e752d924bcfb7f465d8669d5d5a6e78c233", size = 289232, upload-time = "2026-06-28T19:53:40.181Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c8/ca0ac7f09cc88ca61e0c61c53f7db29334f660ffba5d0b52378e7c44723c/regex-2026.6.28-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1758df6fdd8c800620a5638958720e8a635e1da49a2f09df2dd63e94a24ec4a", size = 792332, upload-time = "2026-06-28T19:53:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/8e/92/04ae94cbe0dd1f478b2aef6c46f995bb6946d3e338d4b28605478b66a2b7/regex-2026.6.28-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ad73ecf20c1ef5c975639f8bf845a9370fcf7dada7edc1e3b0bca20e2f8202f6", size = 861743, upload-time = "2026-06-28T19:53:43.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ec/024d7638c807679ff8a0e6081d01d66c7762339af1cac71e45911587ff9a/regex-2026.6.28-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d80c798b0eec6ea3d45f8816a1e8886c5664615d347d89e8c075b576a1b5a5d", size = 906481, upload-time = "2026-06-28T19:53:44.948Z" }, + { url = "https://files.pythonhosted.org/packages/cd/fd/93bfe5af45f0be4fa8983945455c0e6924e1aeb879cde227958869c1e71c/regex-2026.6.28-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a361feeaf1b6ba1df060f2ff5c5947092edf537a35ce78e76387ac56d3e0f4a4", size = 799867, upload-time = "2026-06-28T19:53:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fd/e5d965d41f2398c8ce0f37a4652f03bb297fd009bb796d390134225dda12/regex-2026.6.28-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b92366d9c8bba9642989534073662abdd9b41faf7603a7ae71597833f3b88f0", size = 773632, upload-time = "2026-06-28T19:53:48.892Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/ff39afaec92b9ee2dba0302a4783976005091681069808938c31cf8df3b6/regex-2026.6.28-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:11251768cc23f097dd61b18f67966e70f74da822784d17e12a444eb6b29d4288", size = 781669, upload-time = "2026-06-28T19:53:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/45/4e/e2fd4bb8228e10c24af2d7ff867182372190e498eab9fd29cbe54c403c95/regex-2026.6.28-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad5c67786145ec28a71a267d9f9d92bdc8d70d65541eea852c253f520a01f918", size = 854497, upload-time = "2026-06-28T19:53:52.323Z" }, + { url = "https://files.pythonhosted.org/packages/72/7c/f0340384a973082979064156d05f3d2cc1dced7371efcd7a1b45726a1a8a/regex-2026.6.28-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f1da438e739765c3e85175ede05816cbede3caaacb1e0680568bda6119bfdfca", size = 763335, upload-time = "2026-06-28T19:53:54.024Z" }, + { url = "https://files.pythonhosted.org/packages/e1/32/90ce0d0898e205506cc22b9c81cfb16b722e06ca5f50fad51c053c2a727b/regex-2026.6.28-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d98b639046e51c5de64d9f77351532105e99ca271cb6f7640e1f903d6ab63032", size = 844615, upload-time = "2026-06-28T19:53:56.216Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/55abb149599dce1ade687170557129524011eeb3d92afe02429cea7754a2/regex-2026.6.28-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e164ace4dbab5c6ad4a4ac7c41a2638fe226d0c770a86f2eb041f594bac6ee7", size = 789193, upload-time = "2026-06-28T19:53:57.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/cf7f6f6f152e52fdad978b913bf24c14df647eca0f81ef31f3aee0be8982/regex-2026.6.28-cp311-cp311-win32.whl", hash = "sha256:3169a3159e4d99d9ae85ff0ed90ef3b8906cc3152653b6078b842ace6c8f72c3", size = 266731, upload-time = "2026-06-28T19:53:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cf/a48d8e8d406b22481cad146f48fa0dfca3c5f402b91f26d8e5a0fe4f513d/regex-2026.6.28-cp311-cp311-win_amd64.whl", hash = "sha256:5977295b0a74e8241df8a4b3b27b12412a831f6fa32ee8b755039592cd768c3d", size = 277918, upload-time = "2026-06-28T19:54:01.502Z" }, + { url = "https://files.pythonhosted.org/packages/89/b2/a222392207db7ed86281a732a99f7cf7f2bb35d332799e892b8510be000e/regex-2026.6.28-cp311-cp311-win_arm64.whl", hash = "sha256:f5fbaef40c3e9282ccee4b075f5600a0d858aa0c34147732f1baa69c8188a95d", size = 276876, upload-time = "2026-06-28T19:54:03.411Z" }, + { url = "https://files.pythonhosted.org/packages/da/21/44aa415873032056c43eac21c67285deb2cf66cddb2a964c3cdc8f803efc/regex-2026.6.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:81cc5793ad33a10444445e8d29d3c73e752c8fb2e120772d70fcb6d41df40fe1", size = 490480, upload-time = "2026-06-28T19:54:05.392Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5f/30d4116093c2128099f78b6990dfc1698fdbf3ee528f1e1c647378034c79/regex-2026.6.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e18225243250a1f7d7e5e5d883f3b96465cd79031acf5c6db902b7025f2125d9", size = 292137, upload-time = "2026-06-28T19:54:07.088Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/ca20a0e0de49837e6337603a91ab77556aa27033ac5b975615d98698cfb3/regex-2026.6.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd1638b1c2db1f2d01c182a4b0d3e2e88b0e99910320a745c1727ee3638ddab", size = 289623, upload-time = "2026-06-28T19:54:08.762Z" }, + { url = "https://files.pythonhosted.org/packages/50/11/c013422a7e2c59946df8ac93e792a4922c98287f2a2181341603c78a5d98/regex-2026.6.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4303ebe16b74eeb3fe2715745023266fea92fd44a23f3e7bb2fb48c7a7bbc195", size = 796756, upload-time = "2026-06-28T19:54:10.616Z" }, + { url = "https://files.pythonhosted.org/packages/b0/95/1309645a0e1ee6fb91d954501da57a0b33d50ad2a9acb313702851a7054e/regex-2026.6.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56b856b70b96c381d837f609eee442a1bd320cd2159f5c294b679552fb1a7eaf", size = 865465, upload-time = "2026-06-28T19:54:12.742Z" }, + { url = "https://files.pythonhosted.org/packages/20/06/491802db47c6f5e2904ffa2518ad3ac27fe6bbf5a66d73210a95cc080d47/regex-2026.6.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f74675ab76ab1d005ffba4dee308e53e89efc22be6e9f9fae5b539a3f81bdff2", size = 912350, upload-time = "2026-06-28T19:54:14.508Z" }, + { url = "https://files.pythonhosted.org/packages/5e/60/3ba57840bcc7e2367090360de0c15a5ba6ad22be89314251105f2e943f43/regex-2026.6.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90581684565a93f7258af1e5d3f41ef20d7d7c61f2a428183a342bcb65485e38", size = 801261, upload-time = "2026-06-28T19:54:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/eb/27/af1eb74e9a78c782b3e450b611a595e44906da8a5107e1227f4a7fd0480b/regex-2026.6.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:28f9e6c28f9b90f6f784595a33240a57e181e61b6ee3dc259b25c61e356d1aa3", size = 777072, upload-time = "2026-06-28T19:54:18.128Z" }, + { url = "https://files.pythonhosted.org/packages/20/18/fdd4c883a39e3ed00d669062af1135809bfd3281bf528150849fbd68825b/regex-2026.6.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:378a71d861fc7c8806b04ac5b133d53c0e774f92f5d9663a539872d3fa2b0417", size = 785119, upload-time = "2026-06-28T19:54:20.314Z" }, + { url = "https://files.pythonhosted.org/packages/1c/79/0aabe34b8482dcadf64355f70f96e22eba5ec6c1efb33563f89654f4061c/regex-2026.6.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc199874ecd6267a49b111052250825bfe19b5101b23b2ba80f54efa3e0994e", size = 860118, upload-time = "2026-06-28T19:54:22.368Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2c/c973323306a27c9db7d160e9584eb7e0ece2a96224ccb0d39060558b31f9/regex-2026.6.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b916a10431494ef4b4d62c6c89cab6426af7873125b8cd6c15811bf5fc58eec8", size = 765786, upload-time = "2026-06-28T19:54:24.265Z" }, + { url = "https://files.pythonhosted.org/packages/e3/df/9ca3e378e352242a4cb45573a5e9162c3ee791507702a23966fa559e36b5/regex-2026.6.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e27727fba075f1e4409416d2f537d4c30fc11f012ea507f7bd74d3e19ecb57a", size = 852120, upload-time = "2026-06-28T19:54:25.972Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3e/3e31e255c4971f53cbce6306b5e3c76cbd3735a54f419bb3b2f194e9f68c/regex-2026.6.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:700fc6a7844bb2c4149292ac79d1df8841a00acd4d45cd32c1ebc7bcc1fd0da8", size = 789503, upload-time = "2026-06-28T19:54:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/72/01/d36561c21c3033d7eeb31d51b491916817de7861acefccc5fc9db8a5037c/regex-2026.6.28-cp312-cp312-win32.whl", hash = "sha256:03376d60b6a11aecb88a79fa2be06b40faa01c6693bc31ef69435cd4818b9463", size = 267109, upload-time = "2026-06-28T19:54:29.316Z" }, + { url = "https://files.pythonhosted.org/packages/a0/59/bbbb0591f38b18c65977cd65ce64749eba1c1996c99ac04e900fc30c0dcb/regex-2026.6.28-cp312-cp312-win_amd64.whl", hash = "sha256:fbd2ded482bf99e6651992bbfcde460272724d4bbc49ef3d6b46d9312867ec84", size = 277711, upload-time = "2026-06-28T19:54:31.143Z" }, + { url = "https://files.pythonhosted.org/packages/86/06/be4f6b337d773ae5739a1bc238f97c16926e72017243735853c030f4c628/regex-2026.6.28-cp312-cp312-win_arm64.whl", hash = "sha256:37294d3d7ddb64c7e89184b2894e0f8f0a19c514bc59513d71fe692c3a8d5fc6", size = 277022, upload-time = "2026-06-28T19:54:32.97Z" }, + { url = "https://files.pythonhosted.org/packages/b6/53/d5c1b3cc0b5a0c985563ad6fac93d73ff2b300cb84342d89f044625d6bc7/regex-2026.6.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b295a83426e0e44e9e60fde99789e181bd26788a1890ae7fe2a24c69bb6246ca", size = 490329, upload-time = "2026-06-28T19:54:35.775Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9f/0c3503e819e91ca0e7a901a8e989ebf840ac7c7aea20b1fc7f31b6759f77/regex-2026.6.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c31665c0deb5c111557a1cac8c27bd5629e2f9e7fd5058900a03576c33b601c", size = 292039, upload-time = "2026-06-28T19:54:37.977Z" }, + { url = "https://files.pythonhosted.org/packages/bb/7f/cd004e13fcad23b3794a82307dfd222e6365eb7f598bd3caab148a830bff/regex-2026.6.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6bf295f2c59de77d1ea7de053607ae4dc9ceb3d57bbb6c7ec51ef4acc4ccff94", size = 289488, upload-time = "2026-06-28T19:54:39.545Z" }, + { url = "https://files.pythonhosted.org/packages/73/4c/293fb34586fbcdc47eac436069e9c11f71fae5dadfd4889b475d7d2e5f7a/regex-2026.6.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17c077586770f67e05bbffeba07fbee6b2b22244f4d4caf8d94e59d574befe04", size = 796772, upload-time = "2026-06-28T19:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/92/fa/c0cd1a90b7d12d9dc155cfc8bdea8df9720988ea5b07e8fa1eccbd0ab2dd/regex-2026.6.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e6cb5a61486f9062397d2e189573b39d38ecfaed698fd9fb6e2756a8ebb8762", size = 865467, upload-time = "2026-06-28T19:54:43.485Z" }, + { url = "https://files.pythonhosted.org/packages/4e/db/0b479973046d005a1eaea299d5d536aeecb9488a16d9cbb8286338102e2d/regex-2026.6.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e86e91a2664f44c3a4e363a7d78fb17c27d5046882e30ea5a877f5e89b28d2ba", size = 912345, upload-time = "2026-06-28T19:54:46.091Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5b/d65adfbd02f32212431bca1f06d1e2eb763a20b12978b454bafaf23dacb7/regex-2026.6.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dfd1331c49233998d84fc5f1f4436cf7a435a7655f6cf0f490229bb5c7254e5", size = 801291, upload-time = "2026-06-28T19:54:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/fc/09/2103686defaf9a0a31c1663782359d5b45f42524c64cca681f5481e44a5e/regex-2026.6.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cadea12805a1bce0b091c302b814207be26fb60a9c0e7f9ad2f9e21790a429fe", size = 777106, upload-time = "2026-06-28T19:54:50.326Z" }, + { url = "https://files.pythonhosted.org/packages/85/5a/b57593c0aa23ed269ec332fbcf07852abcb6b746e811d9464e0d09b4e25f/regex-2026.6.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f2c1682b67ad5d2376498f2a5a2a8f782fa2e4a06d0465b5e357799806e8a20", size = 785175, upload-time = "2026-06-28T19:54:52.172Z" }, + { url = "https://files.pythonhosted.org/packages/79/59/c36e756ad29bf14d7b6c6d7138952476b21f6160286cedb98ac13481c993/regex-2026.6.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:64e142eb55e84868087da1375d7c36ff97d55010951849f515322a91d5fef1b4", size = 860186, upload-time = "2026-06-28T19:54:54.11Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/49808aea0da9649c300139360708fb91b7144be1f962fcebf96755fde948/regex-2026.6.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:abb4daabe7be63273787a62dfd6164dadf8f7a63fbec3d2730e5e5e7126d858c", size = 765754, upload-time = "2026-06-28T19:54:56.04Z" }, + { url = "https://files.pythonhosted.org/packages/be/c5/52bbd436cf2200decdf48825fa38363eaaeebb77011ea9928a1ef9e0b9f2/regex-2026.6.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec2b2ad00ab8c16a2798cc8db80c53c4d5b8b3a2441f6cbaef06625f5ca25854", size = 852085, upload-time = "2026-06-28T19:54:57.988Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c3/0390b66e3019497143fe768b3ba567b64d8b24f3812d09506deb86f4a0f0/regex-2026.6.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bfc9677982c914d9085b8e1c3b3ae6e88f139fb56531c2416d6c8f338093c22b", size = 789600, upload-time = "2026-06-28T19:54:59.977Z" }, + { url = "https://files.pythonhosted.org/packages/88/fd/ab5b03653a244975069fed93d73f4f5f7484c03a84cedb238292510d7182/regex-2026.6.28-cp313-cp313-win32.whl", hash = "sha256:bf54bc693fc4e0530e666ba5ec4bcba14dbe8f66b7cfc15c27317d1a6e40b9a5", size = 267088, upload-time = "2026-06-28T19:55:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/68/55/21022f7d3143210ae8d4ff905c45306237b657375cc0b97883f49db3d423/regex-2026.6.28-cp313-cp313-win_amd64.whl", hash = "sha256:e128feaf65bf3d9eb91bec92322a8f7e4835e9c798f3e9ea4b69f4def85620e3", size = 277680, upload-time = "2026-06-28T19:55:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/b6/99/7f664804f1aef924542b0b233996b78b3e4d0a52d9951358aac99f129f51/regex-2026.6.28-cp313-cp313-win_arm64.whl", hash = "sha256:695873e0ea8d3815ea9e92e2c68faf039cc450e2c0a62a31afe2049eb11be767", size = 277017, upload-time = "2026-06-28T19:55:06.29Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e1/9eb83518e159d719fd681c4932dc2aaff855ce72451e1d05d69466f25a96/regex-2026.6.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:189dbf9fc4252d9f1352bf4bd1bef885edb6cc4b7341df202a65f821aaa3891c", size = 494195, upload-time = "2026-06-28T19:55:08.292Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e2/e259c5f2f7be269d0e2fb54275c1fa6a13fb47019f389c3f3ae457447825/regex-2026.6.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9277a4c6503390aa39cb4483b87ec0384faee0850a23b5cea33d008b5d8d83f1", size = 293976, upload-time = "2026-06-28T19:55:10.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/9bdf444014d22b045d0c82ca114fac7e07a597b5b5331b7c4ce6328426e2/regex-2026.6.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17eddca4e8ea9af0b5739314776cdf0172a49731ab61f2e1ea66e066ddd46c97", size = 292340, upload-time = "2026-06-28T19:55:11.88Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3a/f49b11e59cbfe187ace0053a460bd72a0169b8cd52e7db9421a074ce7a43/regex-2026.6.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4466b8641e00c697aab5a73150150d2b2ea96b131c595691f42031abafd9f4d", size = 811704, upload-time = "2026-06-28T19:55:13.612Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fb/ad04c39e149bf8b6cf357df5fff78341733ec366780a00c803a36735818c/regex-2026.6.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cfcd4b0bdcf768c498415c170d1ed2a25a99bf0b65fa253bbd02f68ceba6475", size = 871157, upload-time = "2026-06-28T19:55:15.797Z" }, + { url = "https://files.pythonhosted.org/packages/7f/64/0e5ba31c11eb8ef7aac19a690c1211fc9aa9990caf09565785ebb0081b9a/regex-2026.6.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:80c7adf1ef647f6b1e8aa2ca280e517174cd08bdf7a2e412cdfb68bd6a0917cb", size = 917287, upload-time = "2026-06-28T19:55:18.692Z" }, + { url = "https://files.pythonhosted.org/packages/11/75/6b78df2b858c2fcbbc4858fdc3f2975cf2703be374b2842db7d2c32591a7/regex-2026.6.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a043f5770e82283a22aed4cefef1a4e0f9dd8fd7184cb6ce0ad2e579e2134a9e", size = 816333, upload-time = "2026-06-28T19:55:20.973Z" }, + { url = "https://files.pythonhosted.org/packages/b4/01/ecfe665a3694d5eda9f3ec686c856438ada0943947b6005e90556a1e2cdf/regex-2026.6.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3bd630a8dba06b55254ea5ee862194edab52ec783100d2ef1cd15a9c512fee27", size = 785518, upload-time = "2026-06-28T19:55:23.003Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0a/88f9cd88ff1e82881605c4ffd62d77ee67d051232cfe6f8e9a64b86cf0e8/regex-2026.6.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b77207e3cee13086f1906a6a2a12b41244c577e8ad9370d4b35ae1d548d354f3", size = 801371, upload-time = "2026-06-28T19:55:24.888Z" }, + { url = "https://files.pythonhosted.org/packages/a8/97/601483732f93275482ceb9fed57813dfed7c47d3a019db6ec4a3bb6e23e0/regex-2026.6.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:6de82c268e5d101ee9e3ffd869924aa9a371e3a21e752cf4fa17b6ce50d219f7", size = 866517, upload-time = "2026-06-28T19:55:27.232Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/385c2a0351b994a693453c1d1a6e9af9eb35db3c9460d76b5078acd70c62/regex-2026.6.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b15859e3908544fb99cf47341dcf0bfd089147d258c4c4d8a29e5b087f8085cb", size = 772834, upload-time = "2026-06-28T19:55:29.154Z" }, + { url = "https://files.pythonhosted.org/packages/06/bc/bbf4a5b3b29770d7f307d3c28b5b1bca0105b0cb424be0a4eb1339bc92cf/regex-2026.6.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c91487a917edd48a1ea646fdf60d7936d304f0e686fa7ea8326e47efca51d816", size = 856606, upload-time = "2026-06-28T19:55:32.186Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/51d74fff82f682819979249f8d700267108ba5dc4eb284b0e11b9c85e4b3/regex-2026.6.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4ac65f3e3a99fd8f3a4a74e7a6610acd1ce9dfe9b8a03d346a4922380d68aeb", size = 803475, upload-time = "2026-06-28T19:55:34.328Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3e/6be10cefdc813533fe604dbf5d3c77d2638e7ee658b2749ebadc113b6b2e/regex-2026.6.28-cp313-cp313t-win32.whl", hash = "sha256:3f6316f258bc7e6c9c2acbe9954947bbd397a81be3742a637a555f1855d6618d", size = 269126, upload-time = "2026-06-28T19:55:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3c/32cda905ea1a6eeeb798291c294d8ec66ee0efe0cdba28b061e248b1d396/regex-2026.6.28-cp313-cp313t-win_amd64.whl", hash = "sha256:1484bdd6fba28422df9b5ebb04055b2e1b680e8e4f08490bb21ff0f3cc50d0ab", size = 279961, upload-time = "2026-06-28T19:55:38.456Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b9/69f4e5cd6fbe0bb420cb2dbae441ca118f2495bdda522a74da75aa9829e7/regex-2026.6.28-cp313-cp313t-win_arm64.whl", hash = "sha256:3f15020f0b69cafe57baa067ff65b29acef68ff6b1670a53bef1ca11d708e02d", size = 279266, upload-time = "2026-06-28T19:55:40.62Z" }, + { url = "https://files.pythonhosted.org/packages/3b/fb/fad3b810a5bb1e09b9e5d6913fc6ba88cab738fdf283196827a3c59a4c10/regex-2026.6.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:f7c032b0c8a73739ff8ff1aaf30c281fa19c17bf7f1543256c8507390db7807c", size = 490407, upload-time = "2026-06-28T19:55:42.724Z" }, + { url = "https://files.pythonhosted.org/packages/d6/52/b8c79d12276d93e90e707e939b396034c04980caf1235312ef790f8e11fc/regex-2026.6.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f6710f512c57b84f127a23d0f59560a03b64136eff419ae1be5ab557577fe5e3", size = 291988, upload-time = "2026-06-28T19:55:44.549Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/6a911f18279daa8d7bb8b20d771ddb6ef31fabd35f5921f9d3ba21640e80/regex-2026.6.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c0013958f427bd82509a186b9ff206d66cb8d60a81fc797a4c717afd18c5b0ba", size = 289704, upload-time = "2026-06-28T19:55:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/fd/22/ad1955c47c669291a05804d53d7071cc0732dfdf166857be38003cedc2d1/regex-2026.6.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f06cdcd6421f8e194ad312ea608020381250df9b8a57661c1b57e9e5273878", size = 797017, upload-time = "2026-06-28T19:55:48.166Z" }, + { url = "https://files.pythonhosted.org/packages/e5/67/a83159ff8703ab4d0c2cf99e76ebf289b7b4a501623241d09f88f3614f80/regex-2026.6.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec9689392f7494ff4e3f8e7e8522f9158f11023f337eaaf04a64542fc45bbf26", size = 866112, upload-time = "2026-06-28T19:55:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/7bff2d6dbbd77421b3274aa51db1c887381cbc5b6eda93598c3e882ea345/regex-2026.6.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa084684e6d2078bf6139e374d1fc2af5ddc1ac7122759a2db716d68169f6fd0", size = 911554, upload-time = "2026-06-28T19:55:53.707Z" }, + { url = "https://files.pythonhosted.org/packages/29/44/ae59c3826e7ba492e56795cdf74ea2a7b5b7c5ea116afb79ee4956a5dff1/regex-2026.6.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40455e6840dc4e96a6fe50f4cedc957de2752c954d91e789812be55d49be199a", size = 800665, upload-time = "2026-06-28T19:55:55.875Z" }, + { url = "https://files.pythonhosted.org/packages/d6/19/6fd033d2ab00f35d445aaeaf3307c1e721424dcbfd48f6f65c857cb939cf/regex-2026.6.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:530b5c223b9ca5dd8370ac502e080aee0e4ded32be987c6564b425fb5523d581", size = 777243, upload-time = "2026-06-28T19:55:57.909Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9d/99730f26df4938049ab1e652ca75e967b4c6739444e18d9707bfdb8af20c/regex-2026.6.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e0ed273ecd1a89be84466c1749bfe58609cc2a32b5d5e05006c4625ba96411b", size = 785784, upload-time = "2026-06-28T19:56:00.072Z" }, + { url = "https://files.pythonhosted.org/packages/48/49/105cd57162f5fc5c04cc917a1388a060cf8427e5c14353cd9044660fbf4d/regex-2026.6.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0ab0d5344311fc8e8667078942056c3b9c9b4a4b1cc99f2eb8a5af54554f4acc", size = 860914, upload-time = "2026-06-28T19:56:02.017Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/788245a95b69018f58bff2f4fd27d007cacaea088cdb390979743f1b2571/regex-2026.6.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:eacb79625323d9f7e7925366b917f492b8356fad58f5dc4fa12ff8c21d8f4ca9", size = 765915, upload-time = "2026-06-28T19:56:05.021Z" }, + { url = "https://files.pythonhosted.org/packages/ca/01/292065a39a004b05e67a337b18213670a7cb919d6856ac2d7df7f1a10dbb/regex-2026.6.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20f4d87702702aa1d572721e146f301660c50eef6fd6cb596e48a22b0ace17db", size = 851404, upload-time = "2026-06-28T19:56:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/98/9e/a93d865db0e13483ae1a01d81e2ce16d4a7fe2f9b9fe4aac4cc08590b136/regex-2026.6.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e693940a3b9e6d6e4dc2a54ecaa74b74934f77af1ef95f518a74261ef7cc1bc", size = 789373, upload-time = "2026-06-28T19:56:09.894Z" }, + { url = "https://files.pythonhosted.org/packages/82/0c/38b1685ad4017d78efbc8fa7dbbf96d8113b53750c8aa2d3609defd46605/regex-2026.6.28-cp314-cp314-win32.whl", hash = "sha256:234a51e20ebc18ab83b2c0600cf28f2e884560a0e00f743878f0b7d8e7c4cf03", size = 272496, upload-time = "2026-06-28T19:56:11.83Z" }, + { url = "https://files.pythonhosted.org/packages/55/50/e19f261ff9ba9b50722a529e09b1743ecf65eb348be99d0fd2cd7fcede1c/regex-2026.6.28-cp314-cp314-win_amd64.whl", hash = "sha256:7b15c437bc4604f03ceb3f8d37eae2f8930e320e1bc556b259848c639d9eec1a", size = 280754, upload-time = "2026-06-28T19:56:13.758Z" }, + { url = "https://files.pythonhosted.org/packages/36/b8/c9e68f3a9e33be73f20990b2c065b144ff2d0aa242608a950d8c4f3b56e8/regex-2026.6.28-cp314-cp314-win_arm64.whl", hash = "sha256:c6e6f790d01380a74ad564f216c533b86504afb61bf66f2b2e11e7f1a3e287a7", size = 280979, upload-time = "2026-06-28T19:56:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/03/e6/21c425a37880c650d007c4171c6a80325446d830d85f5fbf335e7205b1e7/regex-2026.6.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3527a72adcbe9e3600f1553b497d397c1a371d227580d41d96c3c5964109b65c", size = 494282, upload-time = "2026-06-28T19:56:18.049Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/6647a7ccf5ffff995ba955a0b7d766440f4e58ce1666549c8ee998f2b972/regex-2026.6.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a644f6408692812f5ead82519eed680e08d5d546fddbd9f7d9514e3c73899aa5", size = 293977, upload-time = "2026-06-28T19:56:20.145Z" }, + { url = "https://files.pythonhosted.org/packages/8c/dc/a3e141a4eaf125e50f63105570c01fa477c06ac5259dcfa95e9b90760e84/regex-2026.6.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8e2fae6bb883648346f84db270dc9aafc29d8e895f62b88a75ccc83b09519820", size = 292432, upload-time = "2026-06-28T19:56:22.345Z" }, + { url = "https://files.pythonhosted.org/packages/35/ee/2ac1a6b9f167f8ff69f5a789938cc103b60cff41b24a6990daced8b88e34/regex-2026.6.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debe623e09cee97ef9404575e936c610aac9bb08358c5099aaef14644a6871f2", size = 811877, upload-time = "2026-06-28T19:56:25.056Z" }, + { url = "https://files.pythonhosted.org/packages/df/7b/9a5505ee92180bcae300b1018b9ff3d3c19962436e66f2505f255e9fde35/regex-2026.6.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc579c91fb4605773483a8d940b136bcc5b854fff44fa14a1572a038f46563f1", size = 871212, upload-time = "2026-06-28T19:56:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/4d/d61a702a9f9d1bd29b22cbef1aed6d477baa961232a7eb4d91b7775b0b3e/regex-2026.6.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7c42be203d84ecf7d487ff23f8a61ef0eb0534fa0fc317a2fce8c065d20618f", size = 917507, upload-time = "2026-06-28T19:56:29.762Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/1308066f5966b65fbb6905b99ba37e9f1cd753dd0ac08485f8257334ee92/regex-2026.6.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8184b4e2fdaf9cdfe77e38f15a4d9dc149168c9c29eb0ea17c5481d3bb80546", size = 816389, upload-time = "2026-06-28T19:56:32.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5c/57ce2cb8d714ee0b7f11c7ee4cfe2af66df2b90f147feadcb538609a3a02/regex-2026.6.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:697f103104f5872d64078d8eeac59979960be8ee76115a2d3f31096312e2a400", size = 785890, upload-time = "2026-06-28T19:56:34.492Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fd/1d5350d3a8a327bff0fccacb911732baf7b5b6f5529c0e3fa602a23e7dad/regex-2026.6.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:714d2b1aa29beef0ddfcdc72ad0771c05326551a8bb0680b0ddf74bfaad87387", size = 801451, upload-time = "2026-06-28T19:56:36.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/79/3c9e4f8a0306e030ad5a43bbbc01625fb28d58a813bc52d42fd1cc63fb2e/regex-2026.6.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0f09f62e450cc2f113018cc8412aeea3a120a04e1ca7e801a0d441583f9a3b06", size = 866504, upload-time = "2026-06-28T19:56:38.994Z" }, + { url = "https://files.pythonhosted.org/packages/65/12/f747de475b54f4709efb24dd0fbc8467c64cec91f5db0d047b079646ee78/regex-2026.6.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:731ea12d5aeb2577eaef2393d6428b995f76eb35f68a89e03e15a97719d1de19", size = 773047, upload-time = "2026-06-28T19:56:41.061Z" }, + { url = "https://files.pythonhosted.org/packages/58/3c/f02f860e0500c1b2d61a79dec7e214b37fb9656281dcddc92397edf96678/regex-2026.6.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:51e952c8783eabd4706d0f63922f219bcfc1bef9b8cb35941c0d1a0396578858", size = 856665, upload-time = "2026-06-28T19:56:43.466Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6c/28b3fa222513484be9dee26b7222bda109056c43ea28aa2314262ca48816/regex-2026.6.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43248fe4c0ab8fbb223588a0795b11268940072c97bba30ea8f9b49d8cdfde34", size = 803573, upload-time = "2026-06-28T19:56:45.791Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/8f86cf1a1fd85c5ab0c503c9fe4607ad4ad48978b2d8b435d94465e134c7/regex-2026.6.28-cp314-cp314t-win32.whl", hash = "sha256:fc1eddc25ad23c0f1344ab280d961ac595ead48292d7c779497975942373f493", size = 274515, upload-time = "2026-06-28T19:56:47.948Z" }, + { url = "https://files.pythonhosted.org/packages/0f/de/f8613c03b36786ddef2c930d28f9bcae861fcd541cc9203a870956cf1e83/regex-2026.6.28-cp314-cp314t-win_amd64.whl", hash = "sha256:ede8d8e53b6dde0a50f7eca902f0af76d87ab02a55aba7542da68ae3e5dfe83d", size = 283650, upload-time = "2026-06-28T19:56:50.614Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f3/f5ec86839bbabe33b6dee649b62ff9a445d43de6b0ad780cf6b83c56f61e/regex-2026.6.28-cp314-cp314t-win_arm64.whl", hash = "sha256:4da6f6a72f8700b97a1a765e837fb7d5750bfd9f13acea7bae498f573e3a70a8", size = 283338, upload-time = "2026-06-28T19:56:52.879Z" }, ] [[package]] @@ -3144,16 +3189,16 @@ wheels = [ [[package]] name = "responses" -version = "0.26.1" +version = "0.26.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "requests" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/58/1fb6de3503428196df78638f991ec8095274f1ee9723e272ee4d9ff0092b/responses-0.26.1.tar.gz", hash = "sha256:2eb3218553cc8f79b57d257bac23af5e1bf381f5b9390b1767816f0843e01dc2", size = 83088, upload-time = "2026-05-21T19:56:39.747Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/1a/4af3e6d659394b809838490b144e4ab8d7ed3b9fecc7ca78f5d2f79b1a3d/responses-0.26.2.tar.gz", hash = "sha256:9c9259b46a8349197edebf43cfa68a87e1a2802ef503ff8b2fecbabc0b45afd8", size = 84030, upload-time = "2026-07-03T16:44:50.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/31/6a620b4427d546b9e7cca8b3b8c5f0559d9cef2bb9eedcda7f73c1473c19/responses-0.26.1-py3-none-any.whl", hash = "sha256:8aacc4586eb08fb2208ef64a9eb4258d9b0c6e6f4260845f2f018ab847495345", size = 35502, upload-time = "2026-05-21T19:56:38.046Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/693e1d9ebf72baa062ded80d837a035b86ce75eda5a269379e9e2b1008a8/responses-0.26.2-py3-none-any.whl", hash = "sha256:6fdfeabd58e5ec473b98dfe02e6d46d3173bd8dd573eff2ccccf1a05a5135364", size = 35609, upload-time = "2026-07-03T16:44:49.1Z" }, ] [[package]] @@ -3189,11 +3234,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.1" +version = "83.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] [[package]] @@ -3317,8 +3362,7 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", ] dependencies = [ @@ -3380,8 +3424,7 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", ] dependencies = [ @@ -3481,8 +3524,7 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.14.*'", "python_full_version == '3.12.*'", - "python_full_version >= '3.15'", - "python_full_version == '3.13.*'", + "python_full_version == '3.13.*' or python_full_version >= '3.15'", "python_full_version == '3.11.*'", ] dependencies = [ @@ -3726,11 +3768,11 @@ wheels = [ [[package]] name = "types-setuptools" -version = "82.0.0.20260518" +version = "83.0.0.20260706" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/bc/73c2c27e047e42f114ac50fb3bdef986c56cbdb68096f8690eeafb839a93/types_setuptools-82.0.0.20260518.tar.gz", hash = "sha256:3b743cfe63d0981ea4c15b90710fc1ed41e3464a537d51e705be514e891c1d07", size = 44999, upload-time = "2026-05-18T06:02:55.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/1d/161cde2b9d56177d04207a67cfdcebaabc881ac9b8f98ef8ca972c630c96/types_setuptools-83.0.0.20260706.tar.gz", hash = "sha256:9e586ac6c1eb504d28b5f06aced1d705e0043839413219ee3c08d16625fbb4ff", size = 45290, upload-time = "2026-07-06T06:16:08.509Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/8f/d5e2d493f09a7a98c95619edda1cb37cee377626c0a869d53274c26f2858/types_setuptools-82.0.0.20260518-py3-none-any.whl", hash = "sha256:31c04a62b57a653a5021caf191be0f10f70df890f813b51f02bab3969d300f20", size = 68444, upload-time = "2026-05-18T06:02:54.582Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/e070aeec63dc76489261282392c6a3422ed914848365fb9f8481894da520/types_setuptools-83.0.0.20260706-py3-none-any.whl", hash = "sha256:42e5bba1bb7c1dc30c1ef3092f0c224dededbf3ae7db525a854bd1a9c6407241", size = 68747, upload-time = "2026-07-06T06:16:07.492Z" }, ] [[package]] @@ -3753,11 +3795,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] @@ -3792,16 +3834,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.49.0" +version = "0.51.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, ] [[package]] @@ -3815,7 +3857,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.5.0" +version = "21.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3824,21 +3866,21 @@ dependencies = [ { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-noble') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-jammy' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-plucky') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-noble' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-questing') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-plucky' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-resolute') or (extra == 'group-10-charmcraft-dev-questing' and extra == 'group-10-charmcraft-dev-stonking') or (extra == 'group-10-charmcraft-dev-resolute' and extra == 'group-10-charmcraft-dev-stonking')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/0e/933bacb37b57ae7928b0030eef205a3dbb3e37afdbdde5be2e113318958f/virtualenv-21.5.0.tar.gz", hash = "sha256:98847aadf5e2037e0e4d2e19528eb3aca6f23906422e59a510bff231a6d32fce", size = 4577424, upload-time = "2026-06-13T20:36:45.066Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/65/ec1d92091671e6407d3e7c1f5801413bb7b2b57630a50cae7750456ba0ed/virtualenv-21.6.0.tar.gz", hash = "sha256:e18a4d750f2b64dea73e72ffde3922f3c52365fabdc8157ebd3da20d031c4734", size = 5526111, upload-time = "2026-07-06T22:49:56.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/87/b0667ede418386ab631e48924b845d326f366d61e6bd08fe68a748fae4d4/virtualenv-21.5.0-py3-none-any.whl", hash = "sha256:8f7c38605023688c89789f566959006af6d61c99eeeb9e58342eb780c5761e5e", size = 4557937, upload-time = "2026-06-13T20:36:42.967Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e7/2fbd0cc1653c53eed8f10670538bb547de2b3e37aacad283faa82a71094b/virtualenv-21.6.0-py3-none-any.whl", hash = "sha256:bce9d097950fef9d81129b333babfb7767072850c2f1acce0ec536708401bfd1", size = 5506216, upload-time = "2026-07-06T22:49:54.941Z" }, ] [[package]] name = "wadllib" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lazr-uri" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/54/82866d8c2bf602ed9df52c8f8b7a45e94f8c2441b3d1e9e46d34f0e3270f/wadllib-2.0.0.tar.gz", hash = "sha256:1edbaf23e4fa34fea70c9b380baa2a139b1086ae489ebcccc4b3b65fc9737427", size = 65960, upload-time = "2024-10-11T13:32:52.169Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/f8/c7681d8d8dca6aa1f90e054bde451c25741fde702b21c347fe65fb813543/wadllib-2.1.0.tar.gz", hash = "sha256:69c60a188c9c629a0e947dfafd89acdc8b7d8d404a6b5eb0ad258fef29362501", size = 66685, upload-time = "2026-07-01T10:48:03.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/a1/3789e82241db565f25e2c6a69c6ebe79f3db84431e93760a4abb451822bd/wadllib-2.0.0-py3-none-any.whl", hash = "sha256:f8e0fc4f19c2c96b3ca8a091f04c86ca196ec9590d44c4d864320aa9533473ea", size = 61733, upload-time = "2024-10-11T13:32:49.289Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/00b61e720165f73c6ef3c7a6ea732314c8ca1ba95b9ed1545fd788d9af9b/wadllib-2.1.0-py3-none-any.whl", hash = "sha256:41b58db0bb5fb21e188c7452281ee7b364b2690a4f6c388afea0ceaeafed1132", size = 61997, upload-time = "2026-07-01T10:48:01.37Z" }, ] [[package]] @@ -3960,70 +4002,118 @@ wheels = [ [[package]] name = "websockets" -version = "16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, - { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, - { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, - { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, - { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, - { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, - { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, - { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, - { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, - { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, - { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, - { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, - { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, - { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, - { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +version = "16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/02/b9a097e1e16fee4e2fd1ec8c39f6a9c5d6257bae8fa12640caf869f54436/websockets-16.1.tar.gz", hash = "sha256:299468cbe42e2b9981134c7c51d99387d8a7bf562b00183b3eec53f882846dad", size = 182530, upload-time = "2026-07-10T06:32:57.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/31/cd11d2796b95c93645bac8e396b0f4bac0896a07a7b87d473bfc359f02c3/websockets-16.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de72a9c611178b15557d98eabd3101c9663c4d68938510478a6d162f99afd213", size = 179772, upload-time = "2026-07-10T06:30:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9b/34306d802f9b599eab041688a2086318037560cfae616a860234cca575b6/websockets-16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:37b0e4d726ffea3776670092d3d13e1cb605076f036a695fd1259de0d9b9fe02", size = 177457, upload-time = "2026-07-10T06:30:24.636Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/36ebbb978a7af70ff952afe5b22561264967164e9ad68b6734cae94efeb4/websockets-16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:00d50c0a27098fcb7ab47b3d99a1b1159b534dbcd959fbf05113ebc37e5f927b", size = 177737, upload-time = "2026-07-10T06:30:25.954Z" }, + { url = "https://files.pythonhosted.org/packages/17/d7/944f341d0d3c0450ffd3d171479531df1818cb1df1623af4065113999c44/websockets-16.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1acb698bff1da1782b31aebd8d7a24d7d05453964abcd7d03dbf6e25893908e8", size = 186244, upload-time = "2026-07-10T06:30:27.235Z" }, + { url = "https://files.pythonhosted.org/packages/32/e5/a9b98fc49ef0214718a9c839c6c63856a921877256ec46f371be32decfa8/websockets-16.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc2c453f3b5f99c56b16e233aad5299860558487d26adb2ed27a00c14ca24b8c", size = 187484, upload-time = "2026-07-10T06:30:28.615Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7a/a575b52ca090b1976ffbe4b5f0762d03f399dfcb48eab883101331be71a9/websockets-16.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1a9f08a0728b0835f1c6abe1d9b746ab3de49b7336a0e1919cf96be1e76273eb", size = 190143, upload-time = "2026-07-10T06:30:29.91Z" }, + { url = "https://files.pythonhosted.org/packages/7c/40/705fbbd5677242fd36f724e9a94103e6bbdcb7d71e8f4498bfc1a8a7d413/websockets-16.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a089979d6173b27af18026c8d8b0077f83669a9169174482c4651e9f5739a5b6", size = 188004, upload-time = "2026-07-10T06:30:31.357Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0c/58227c8d66b1c4060c53bac8e066fb4fe2603060408e934f48660a448d72/websockets-16.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a3c18dba232ec2b92a68579c9fed8ff5a18f853d1e09fc0b6ca3159e94f689fe", size = 186689, upload-time = "2026-07-10T06:30:32.712Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d0/5c1314782594aa347e0f18808ee277a61986a2a2f9f470df9893183995bd/websockets-16.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c1eb7df4170d5068892a8834fb5c07b9552353deb0dbeb0bff3820481ae4792", size = 184559, upload-time = "2026-07-10T06:30:34.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e6/109c6f16850fd674b7e3d0e58b8987f05d3881abaa25f42a9faf5e85f097/websockets-16.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c522bd48e625b6d557aa228967258d6d3da031c4cc21d3352fb302479aa9ba0a", size = 186997, upload-time = "2026-07-10T06:30:35.397Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ec/6afa1aebc59426438b85cf7a3868c53a89005e2250a648c99e99943b90a4/websockets-16.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d106396927a7f00b0f3a69215c3357f87bf0bca6844247121f7e8291e826a3b1", size = 185621, upload-time = "2026-07-10T06:30:36.88Z" }, + { url = "https://files.pythonhosted.org/packages/27/24/c038fe8682e9345bfa422d2cc5cc68b0491ab942c92e176bf8dfa6e8331f/websockets-16.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d71bed12909b8039955536e192867d02d76cd3797cedfd0facf822e7668636c3", size = 187384, upload-time = "2026-07-10T06:30:38.096Z" }, + { url = "https://files.pythonhosted.org/packages/30/ca/dc0ef2be39c67394e24bc982a0af59cd6249bf2f4e4272813c5c505d0da9/websockets-16.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:9c1cf6f9a936b030b5bed0e800c5ee32069338129084546baf5ff5014dc62fa9", size = 185258, upload-time = "2026-07-10T06:30:39.579Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/3ffecd83ca3404b41fbdf8e9b178e55b529cd59bf64ea08b5a37b616b568/websockets-16.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3fd3e6a7af2c8fcdcf4ffbeaf7f54a567b91a83267204187797f31faaa2a4efa", size = 186050, upload-time = "2026-07-10T06:30:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/75/26/2e068497c78f31591a610ab7ef6d8d383ecadbe98f9121e1ebda77ef6d2b/websockets-16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dddd27175bf640acae5561fa79b77e8ec71fc445816200523e5c19b6a556fb72", size = 186273, upload-time = "2026-07-10T06:30:42.309Z" }, + { url = "https://files.pythonhosted.org/packages/44/ab/4dc049cb2c9e1be3a2c6fef77118f9c5049979e99cd56a97759d2f40f980/websockets-16.1-cp310-cp310-win32.whl", hash = "sha256:cce36c80b3f2fede7942f1756d3d885fa6fa086766c8c1bcf00695ab80f0d51a", size = 180157, upload-time = "2026-07-10T06:30:43.565Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/5e010ce5f66a8e5df380843f704ada508195a021c0c8a0f933639c9ee1c0/websockets-16.1-cp310-cp310-win_amd64.whl", hash = "sha256:115fc4695b94bb855995b23fb1abcb66099a5995575d3d5bc5605a616c58d0eb", size = 180458, upload-time = "2026-07-10T06:30:45.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d47429afcc2c28616c32640009c84ea3f95660dab805766345b9682468e0/websockets-16.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9b1d7a63cba8e6b9b77e499a81eab29d31100298d090ad4507d1048c0b9cae0", size = 179770, upload-time = "2026-07-10T06:30:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c7/2f0a722039a1e0107be73ed672ba604449b4956e48733e8e6b8a005aea42/websockets-16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bedbc5efeb96621aa2921d2d92608246691399418cac22acba427eb11877ea1f", size = 177455, upload-time = "2026-07-10T06:30:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/43/6a/c26b0ae449e93d256ce5cdd50d5fe97b575a63e8dcd311a1faa972fd6bc6/websockets-16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd847ab82133015afe65d778e7966ab42dba16bd7ad2e5b8a7918db6539f3f94", size = 177731, upload-time = "2026-07-10T06:30:49.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/3f/381550b344a02f0d2f84cda25e79b54575291bc7022128a41163fe8ba5b0/websockets-16.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2fb33ccb16ee40a95cc676d7b0ff451a9a2632f11a0dbc2e666326892b2e1de", size = 187066, upload-time = "2026-07-10T06:30:50.505Z" }, + { url = "https://files.pythonhosted.org/packages/4a/87/5ab1ec2086910f23cfb9ec0c1c29fbcc24a9d190b5198b1557c00ce4a47e/websockets-16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f15b6d9ea9c2eaf6ccab964a082b09bfa6634a495bb0c2e9e7ee6943f58976", size = 188301, upload-time = "2026-07-10T06:30:51.835Z" }, + { url = "https://files.pythonhosted.org/packages/75/4b/bbbb8e6fac4cfc53d7aaa69a3d531bf10799354b0021f4b58914aced8c1a/websockets-16.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:638cf57c48b4ad8ac1ff1e453f4f97db2426b690ddc111e6da96b27b4a340bc3", size = 191594, upload-time = "2026-07-10T06:30:53.229Z" }, + { url = "https://files.pythonhosted.org/packages/5c/da/6c0c349443d6e999f481e3d9a0e57e7ac2956d75d6391bec24b92af3fe13/websockets-16.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c1c85f61bc9d5eac57ce705d848dc2d2ce3680638300bf4e1da7d749e2cf4ce", size = 188862, upload-time = "2026-07-10T06:30:54.744Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ea/a368d37c010425a5451f42052fe804e754e23333e8448aef5d55c8a8d64f/websockets-16.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:eeab6d27f51c7e579023c971f5e6dff200deadf01faf6831beaecd32052dfaef", size = 187633, upload-time = "2026-07-10T06:30:56.055Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4e/2ecd59add10d0855ec03dbdedfcdacdbd1aaabcd44b7dcbeda27538662e9/websockets-16.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2ed64e5a97b0b97a0b66e18bfe281317a75fbbd5afe692f939ea8d14a4292f2c", size = 185089, upload-time = "2026-07-10T06:30:57.444Z" }, + { url = "https://files.pythonhosted.org/packages/6f/eb/c6c3dcd7a01097bb0d42f4e9ef21a2c2a491d36b77cd0870ab59f9e8e77f/websockets-16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b3b021d0ed4bc16eea9775f62c9fa71acdacba0fc790b38581754dedf29ca60", size = 187790, upload-time = "2026-07-10T06:30:58.731Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3e/775d36885d5e48ab8020aaf377de0ff5fbeb8bc2682a7e46419e4a14521c/websockets-16.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6eb604a4167f0a0d53c2243dfc667a29f0b43c3436057184e070bb82a1000fa2", size = 186381, upload-time = "2026-07-10T06:31:00.355Z" }, + { url = "https://files.pythonhosted.org/packages/ad/90/6305c00812a92e47d0582604c02bd759db0118bbafc13f707d712dbcf898/websockets-16.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9a3f125e44c3e34d61d111652e608e0f5b85ce08c225c8d56ad0eb822fa40030", size = 188193, upload-time = "2026-07-10T06:31:01.677Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/96bf8302c81d961585b4d34a2ddd3f229782f9b8c57bc78bbf98f1b1a4ac/websockets-16.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8fdf0b00d0d1f30d1f06a92cab46fe542eec3eb302a7aee7163f142d0780f216", size = 185771, upload-time = "2026-07-10T06:31:03.062Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1f/e8fe44b1d2dc417d740d9959d28fd2a846f268e7df38a686c04ac7dfe947/websockets-16.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:67b56828712f5fa7852de4c0265c28827311a657a4d275b7312ed0d1a918bee4", size = 186803, upload-time = "2026-07-10T06:31:04.34Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/b07d3a4e1eb2ab03e94e7f53f0c7a628e85fde6ad86011f7afd08f27b985/websockets-16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39c7e7730be33b8f0cd6f0aa8e8c82f9cdd1813f159765e073b2ece65f4824b5", size = 187041, upload-time = "2026-07-10T06:31:05.567Z" }, + { url = "https://files.pythonhosted.org/packages/a6/fd/e0abb8acc435642ac4a671490f6cf781c882f3fe682cdced9080ea455ab5/websockets-16.1-cp311-cp311-win32.whl", hash = "sha256:c54fe94fb2f11e11b48920c5f971e298cec73ac35db56efe57a49db63dfc95d4", size = 180158, upload-time = "2026-07-10T06:31:06.929Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/85574d9458d3b913090087b817df0cc47b68e9a01dd0ab6ac04b77f49b0a/websockets-16.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9f4fb9ae8b802e55609685db98382d48fd3feb1397804e1e774968dea0f28c7", size = 180456, upload-time = "2026-07-10T06:31:08.247Z" }, + { url = "https://files.pythonhosted.org/packages/a1/52/748c014f07f4e0e170c8932de7e647a1511d5ab3049cd978797136aee577/websockets-16.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b6aa3f7ad345cf3862c21f4fbf2ef5e14d911348476c2845e137c091fe3a3f0b", size = 179798, upload-time = "2026-07-10T06:31:09.664Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5e/2a2e64d977d084e49d37c187c26c056daaff41965be7300cd5dbde6f8b07/websockets-16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b43fcfb521ac2f34ba80b7b8ea16303e4ad82dd8af667bf40839ad3a5d37b164", size = 177478, upload-time = "2026-07-10T06:31:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/5b85b4e75d697e548a94962ce5c036b05dd21cb9545759d555c5586422fc/websockets-16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2bd3e12cd9afbe2baedae0b1eeade8ba64329b60fe2f9abdc966bd10fd2c2ef5", size = 177746, upload-time = "2026-07-10T06:31:12.386Z" }, + { url = "https://files.pythonhosted.org/packages/9d/62/79b1c8f0cee0da648b4899e1c5b0dbd3aa59846985136a54854db6827ab4/websockets-16.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f41979c8623df9bd30d949d82010a8fda5c56ff12cd8508a5b7272b6d4b53a", size = 187345, upload-time = "2026-07-10T06:31:13.754Z" }, + { url = "https://files.pythonhosted.org/packages/25/34/b7c5c52c2f24280e1c017acb7ad491a566750a5cceca7f3cf999373bba21/websockets-16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a24d1f35aef07d794a16c853c688e74956c50239bec37b4f2de080056046419b", size = 188581, upload-time = "2026-07-10T06:31:15.075Z" }, + { url = "https://files.pythonhosted.org/packages/bc/37/604193bebcbeffe96fdf795960b83a15d600880c64dc17ec9c31c5b3427d/websockets-16.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c64c024ddf7a35331b21fcddb562a039c275d2c82e8c2d12939e7da23997270", size = 191362, upload-time = "2026-07-10T06:31:16.395Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b4/5ee27575b367d7110d4d13945e2a9de067ec84dc71e54b87f01e38550d9a/websockets-16.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e99757f5baafe20fc598e202ea6f5b0b265186ad38d0a17bd8beca16296955", size = 189216, upload-time = "2026-07-10T06:31:17.776Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/3e2dcc78d85fc5d9d814895ce6d07d0dfacc0f6aaa1d151f2b8c8d772299/websockets-16.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:353f3bc6e058ac1ccab4b3588e8598837a8c04cfc8351233e6d523be675d844c", size = 187971, upload-time = "2026-07-10T06:31:19.152Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2f/cd271717b93d5ee19626cb5e38a85baab745c86e33db7c31a3ac729b31b8/websockets-16.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0352f5b38b40e857b6428d468fa21dbb4dd4a567d933c26d9831b4efe1b92f43", size = 185381, upload-time = "2026-07-10T06:31:20.665Z" }, + { url = "https://files.pythonhosted.org/packages/78/91/6ad6f2f1426317b5001bd490534208c7360636b35bac1dec2e0c22bfc40e/websockets-16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70bd789afab579602968c39f21cb925466505f3edff22f0ae852bca54978a4f9", size = 188015, upload-time = "2026-07-10T06:31:22.024Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6d/533733132ab4c07540efd4a8f0b9a435d3a5059b2f26cc476ace1abf7f45/websockets-16.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d0fb4b46f121eccd539353baebd1083a8767a9a351109453d1d1caecd1ba40c2", size = 186619, upload-time = "2026-07-10T06:31:23.376Z" }, + { url = "https://files.pythonhosted.org/packages/08/73/16c059f3d73b3331eba10793704afa4faa9939234fb08ef7dca35794e8f0/websockets-16.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c14b6634af01541e4efe2954fd8f263386f7aa6d37c01e55dd8109fd17661452", size = 188497, upload-time = "2026-07-10T06:31:25.024Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/9a8fae7dd2acdcfb1a8844c29fe42b518a04b64fce38a0923b6290e452f1/websockets-16.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a58532c49a851bcb481e58c1be23b315c17fe2fbbed509d75aeea12f543d2c15", size = 186051, upload-time = "2026-07-10T06:31:26.291Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/b240c7dd6a0e0c59c1f68377cc3015263521080c327c15f5e753c1f6d378/websockets-16.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4e969170c3b08e1d8dabd990fef1fa702c4233aeaabec33f871806e444f6a0e4", size = 187029, upload-time = "2026-07-10T06:31:27.605Z" }, + { url = "https://files.pythonhosted.org/packages/50/35/524e3fac40e47d6fdcf6c4b2c95ef1bc8a97e01593c90eff86621df7b716/websockets-16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff9b000064b88787ba9f7a3cb2af2b68a658ca5aad76458a46469e7124b678a0", size = 187308, upload-time = "2026-07-10T06:31:28.927Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/56840cf62c8859af6ba22b9529da937332468c80f32b598753e8a66d3990/websockets-16.1-cp312-cp312-win32.whl", hash = "sha256:b9f5d83f80f4d7c4bba6d97f3755ac05850c784dce0fd2ab371c4e41172f53ff", size = 180161, upload-time = "2026-07-10T06:31:30.316Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ff/87eb9eb44cb62424a8d729834f2b0515a47e2669fabec29820268f4d50a1/websockets-16.1-cp312-cp312-win_amd64.whl", hash = "sha256:6852c9f653966c16109d3b6f31181fd734f7914927e3f0fa1117af7a18c9aa21", size = 180462, upload-time = "2026-07-10T06:31:31.708Z" }, + { url = "https://files.pythonhosted.org/packages/d9/63/df158b155420b566f025e75613424ad9649a24bcb0e9f259321ab3d58bea/websockets-16.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b0232ed141cec3df2af5a3959a071c51f40036336b0d37e17faf9ef52fc73e47", size = 179791, upload-time = "2026-07-10T06:31:33.108Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/00fe9414dfeafa6fe54eae9f5716c8c8e9ac59d192be3b893c096d395846/websockets-16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a71b73d143991714144e159f767b698f03c4a70b8a65ae1733b650cff488045b", size = 177472, upload-time = "2026-07-10T06:31:34.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/76/b10633424d40681b4e892ffd08ca5226322b2426e62d4ab71eae484c3a32/websockets-16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:187323204c3b2fc465e8fc2609e60437c521790cb9c1acb49c4c452a33e57f37", size = 177737, upload-time = "2026-07-10T06:31:35.964Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/d3bb03b2229bb1afd72008742d586cf1ea240dce64dd48c71c8c7fd3294c/websockets-16.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dba74233c8c3ce368850818c98354dad2570f57231b3fd3bd00d7aa57628881", size = 187403, upload-time = "2026-07-10T06:31:37.496Z" }, + { url = "https://files.pythonhosted.org/packages/26/16/cc2e80478f688fc3c39c67dc1fac6a0783858058914ebc2489917462cb42/websockets-16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63339bc8c63c86a463177775cb7c677691f5bcfac7b3b2f01b286d42acd41600", size = 188639, upload-time = "2026-07-10T06:31:38.86Z" }, + { url = "https://files.pythonhosted.org/packages/15/d6/ad87b2507e57de1cbf897a56c963f2925962ed5e85fbe06aaa83ced27acd/websockets-16.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23e545ea8ae4263e37cdfd4e22a217f519e48e432728bc461185bbf585f38a83", size = 190078, upload-time = "2026-07-10T06:31:40.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1a/5b37b3fd335d5811f29fc829f2646a3e6d1463a4bf09c3100708684c766e/websockets-16.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2237081454846fb40403a80ba86d82e2038b9c45865ab96af0abe7d002a91045", size = 189267, upload-time = "2026-07-10T06:31:41.523Z" }, + { url = "https://files.pythonhosted.org/packages/42/98/06afc33e9450d4230f94c664db78875d90f5f6a5fb77f0bc6ec15ae74e1c/websockets-16.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5f5218de1ed047385ca53744caba9435d65f75d008364970a3fae95a05812cf9", size = 188022, upload-time = "2026-07-10T06:31:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/42fef5d5887c18cf2d148b02debf56cecb9cfbffc68027cde9b12c8f432c/websockets-16.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75c98e3920039d0edff03b74478ada504b7ce3a1bc406db2cabfca84320f7baf", size = 185435, upload-time = "2026-07-10T06:31:44.219Z" }, + { url = "https://files.pythonhosted.org/packages/a0/9b/8021c133add5fe40ed40312553a6cd1408c069d7efe3444ad483d4973ed3/websockets-16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1facd189d8190af30487a55b4c3688484dd50801628a3b5b2ccd26db08e67057", size = 188080, upload-time = "2026-07-10T06:31:45.986Z" }, + { url = "https://files.pythonhosted.org/packages/69/54/1e37384f395eaa127383aab15c1c45e200890a7d7b99db5c312233d193e0/websockets-16.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cc0c6a6eef613c7da32d4fb068f82ef834b58134f6a16b54e6c1e5bf9529ab3d", size = 186678, upload-time = "2026-07-10T06:31:47.449Z" }, + { url = "https://files.pythonhosted.org/packages/68/79/1caeacab5bc2081e4519288d248bc8bd2de30652e6eaa94be6be09a1fe5b/websockets-16.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ad9411eded8988b879be6038206698bf7106c85a78f642c004485bcb95be17eb", size = 188554, upload-time = "2026-07-10T06:31:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/ee/83/b3dca5fad71487b726e31cb0acf56f226792c1cc34e6ab18cbf146bd2d74/websockets-16.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cd68f0914f3b64694895bc5e9b14e8b447e41d7bf5ffaf989bb8dcb5e2dfdce7", size = 186109, upload-time = "2026-07-10T06:31:50.508Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0b/8f246c3712f07f207b52ea5fb47f3b2b66fafec7303162644c74aed51c6a/websockets-16.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fef2debfe7f7ebdda12176f26166f95b7af17af05ba06150fcf889032e0213e9", size = 187061, upload-time = "2026-07-10T06:31:51.861Z" }, + { url = "https://files.pythonhosted.org/packages/47/eb/27d6c92a01696b6495386af4fc941d7d0a13f2eab2bf9c336111d7321491/websockets-16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3cd6c9b798218798f4bb7b2e71c38f0e744bb94ca537b13376f88019d46384d", size = 187347, upload-time = "2026-07-10T06:31:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d5/eeee439921f55d5eaeabcea18d0f7ce32cdc39cb8fc1e185431a094c5c7b/websockets-16.1-cp313-cp313-win32.whl", hash = "sha256:84c170c6869633536921e4474b1cce7254c0c9b0053ef5725f966cee47e718e4", size = 180149, upload-time = "2026-07-10T06:31:55.058Z" }, + { url = "https://files.pythonhosted.org/packages/a3/03/971e98d4a4864cf263f9e94c5b2b7c9a9b7682d77bfbba4e732c55ee85a9/websockets-16.1-cp313-cp313-win_amd64.whl", hash = "sha256:bef52d327d70fa75dad93ee61ea2cb1d1489aca9f35c188833563f5a3b4df0a5", size = 180458, upload-time = "2026-07-10T06:31:56.767Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e6/da1dc11507f8118145a81c751fe0c77e5e1c11b8554496addb39389e2dc2/websockets-16.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f881fca0a45dd6789939bd6637cd98169b92f1c3fdc78262f2cb9ec2cb1f324e", size = 179833, upload-time = "2026-07-10T06:31:58.19Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ac/c0d46f62e31e232487b2c123bc3cfd9a4e45684ca7dc0c37f0987f29baae/websockets-16.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c379d5b207d3a7f0ba4c2e4602a895b0bcc63fb5f5371a4ae7fbddb03b672b", size = 177524, upload-time = "2026-07-10T06:31:59.563Z" }, + { url = "https://files.pythonhosted.org/packages/4a/33/abd966074b34a51e4f134e0aaed80f5a4a0a35163ea5ac58a1bc5a076d23/websockets-16.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:98ab58a4faa72b46da0127ccc1931dcbfc0985b0778892300a092185910c4cbe", size = 177743, upload-time = "2026-07-10T06:32:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/ea/30/646e47b8a8dff04e227bdab512e6dde60663a647eeac7bbd6edddd92bbc5/websockets-16.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e9c4e369fc181b2d41a99e01477215cecdc8546a39f7d41a59cc0a7065a0b09", size = 187474, upload-time = "2026-07-10T06:32:02.54Z" }, + { url = "https://files.pythonhosted.org/packages/d2/72/890ab9d77494af93ea65268230bfbc0a90ba789401ed7a44356a44785644/websockets-16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0704df094b2d5fa7f6f410925a594c2a5c9a09167731a76292e5410934208209", size = 188717, upload-time = "2026-07-10T06:32:04.156Z" }, + { url = "https://files.pythonhosted.org/packages/d5/aa/baedbbaa6bf9ed6029617ed5e8976535bd805f483ca9b3484e7ad9ee08bf/websockets-16.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b22b1f4950f6ab7126623329c3b47b3b90a14c05db517f2db2a026ad6c928352", size = 190090, upload-time = "2026-07-10T06:32:05.822Z" }, + { url = "https://files.pythonhosted.org/packages/52/4f/d813ec94e18002571ef4959d87a630eff6e01b72a51bcb0832b75ae8c51a/websockets-16.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1ae4a686a662964a6671069f84f7f908cc3475e782227726b0c622c715962105", size = 189320, upload-time = "2026-07-10T06:32:07.223Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3c/8ec52a6662f3df64090fba28cd521d405d54759268d8e820477037e8c80d/websockets-16.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:856bdd638f8277f86465057bfdd4da097c73058fb0f9d2bd5baea29e2bf2d367", size = 188068, upload-time = "2026-07-10T06:32:08.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/7f/f0ae6042b14f86fa5f996c6563ea4cf107adc036ccbedc9d4f418d0095f9/websockets-16.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9003a1fde1c21a322a3ca3fa0c4bda8c639da81dbc925162766086643b05ba87", size = 185493, upload-time = "2026-07-10T06:32:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/89/ad/5ffc53af9939c49fd653d147fa5b8f78ced1f6bce6c49a7446860945b0ce/websockets-16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39e947b1f5fdab045174306e3916785bf3ed537648acc1549827c08c33b10953", size = 188141, upload-time = "2026-07-10T06:32:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/67/62/729206c0ee577a4db8eae6dd06e0eef725a1287c6df11b2ef831d003df31/websockets-16.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5dd0e666b5931c0509cf65714686a1c5126771e663a79ac5d40da4f58b1f9502", size = 186653, upload-time = "2026-07-10T06:32:12.845Z" }, + { url = "https://files.pythonhosted.org/packages/1b/86/e8806a99ec4589914f255e6b658853fe537bf359c05e6ba5762ad9c27917/websockets-16.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a0285df7925657ad65a65fb8dc330808bce082827538fd50ef45fa12d1fc5bca", size = 188614, upload-time = "2026-07-10T06:32:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/89/38/ac554e2fc6ff0b8deeff9798b92e7abd8f99e2bd9731532e7033de208220/websockets-16.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:82d1c2cab3c133e9d059b3a5420bed9376bd30e21c185c63dda4ddadf6ddda47", size = 186165, upload-time = "2026-07-10T06:32:15.626Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c5/4ef4d8e53342f94f3c49e1ae089b32c1e8b3878e15e0022c7708c647f351/websockets-16.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c39907f1eaf11f6277def65aa02d68f30576b693d0c1ca332aafa3caa723ac6d", size = 187119, upload-time = "2026-07-10T06:32:17.114Z" }, + { url = "https://files.pythonhosted.org/packages/3a/33/4788b1dd417bd97eeb2698af3b9df6775ac656f96e9987da0419a067602f/websockets-16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:45c5ea55446171949eb99fd34b771ceddd511ca21958d40d0197ced33159e5ee", size = 187411, upload-time = "2026-07-10T06:32:18.629Z" }, + { url = "https://files.pythonhosted.org/packages/30/38/00d37aad6dc3244ce349e2864815362e50b3cfc00cac28d216db20efe40f/websockets-16.1-cp314-cp314-win32.whl", hash = "sha256:b8ef8b1c8d6bd029a475ac432e730fba2dfd456715d26c473e2a82291024b99c", size = 179822, upload-time = "2026-07-10T06:32:20.233Z" }, + { url = "https://files.pythonhosted.org/packages/9d/37/2a8cb0eaddee5eaebda47a90a3ba0898d1ce3d866b02a4857fea17d82e5b/websockets-16.1-cp314-cp314-win_amd64.whl", hash = "sha256:7358ff21632b5d062707f73e859c824f1c3807e73d8ca25e71caca7c4cdcf145", size = 180167, upload-time = "2026-07-10T06:32:21.749Z" }, + { url = "https://files.pythonhosted.org/packages/07/5a/262ad5fcaef4198997b165060f09a63f861e76939b1786ab546ccc3f8120/websockets-16.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d0f38f4c3e9b359e257c339c2cc1967ccaeedb102e57c1c986bdce4bf4f32268", size = 180166, upload-time = "2026-07-10T06:32:23.278Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/36377db690f4292826e4501a6dec2801dc55fd1cf0405923b04937e478df/websockets-16.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3c3d2cbd1602593bad49bd86fa3fbb25407d87a3b4bf8857c0ac5ac4914e1901", size = 177697, upload-time = "2026-07-10T06:32:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c7/07171abce1e39799a76f473608580fe98bd43a1230f5146159622c02bccf/websockets-16.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:36069b74671e7e667f48a7484249f84c45a825a134c8b1bdc01875d0daa10d79", size = 177902, upload-time = "2026-07-10T06:32:26.564Z" }, + { url = "https://files.pythonhosted.org/packages/14/17/c831f48e250bc4749f57c00dcce73337c41cd32f6d59a64567b84e782601/websockets-16.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:587f83c2ce8a5d628e166384d77fa7f0ac69b9007d515ab442123e6615aa8da3", size = 187766, upload-time = "2026-07-10T06:32:27.981Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2e/4dfe63e245b0ecfaf470cf082d25c6ce35808159135fd88c82653a6b11ab/websockets-16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6db7972d52bc1b66cefe2246902e256cbaebc9ba8a45eac09343d7eb6671b2", size = 188939, upload-time = "2026-07-10T06:32:29.365Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e5/5faf65aebd9562f6b4bc473d24ce38cc56f84eb5f5bee66ed9b86733f93c/websockets-16.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e7d6014888a0632e1ed7a4095248bb3095232999447f2d83bfb1900987dd9ed9", size = 191081, upload-time = "2026-07-10T06:32:30.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/cd/2634f2f2c0556c1aae6501ed6840019cc569dd6fdbcac6494378daea4dc0/websockets-16.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cb074d150e4ad2a77aa8a332c2be85f3f64f2681519d2570c1225c12c9821ff", size = 189513, upload-time = "2026-07-10T06:32:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/2c700b51196104f09715b326b1f092ed25326bdf79a03e00a4842e503743/websockets-16.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d19c9067e1fe9490f974bffbc0e443b80a7674c5efb4980c429cc00771f07c5a", size = 188240, upload-time = "2026-07-10T06:32:33.897Z" }, + { url = "https://files.pythonhosted.org/packages/f1/20/86283636e499a1a357fa9441f690ba34f255e731f2fea174132b3b762b57/websockets-16.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d440ff0c6c7469ad59c0a412c383c235935b43635e89425e3f6a0c36de90c31b", size = 185955, upload-time = "2026-07-10T06:32:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/91/23/d7fb734b0095d43bc7f1c9f68afd50adb4176e7e513403e8c70ad7daa4fa/websockets-16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8613129a2533f08de24505e69a3e403cedaadae49abdb043c4d170ca71b7e4bd", size = 188491, upload-time = "2026-07-10T06:32:36.673Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5e/168a192689db468405ecf3b8e4a2c18811936b0724d017ad7e6d252734f0/websockets-16.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a5bf9c23f197b4ec88290fd5463f33db67362a1bb10f85fc2e8e7627f0ddab97", size = 186983, upload-time = "2026-07-10T06:32:38.207Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9b/66795fa91ebe49019ebe4fa910282172252e37046b80e08fc52e0c365150/websockets-16.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:520b0fd0395f075febb283c76755af724ab9fd19dffa4f3bfd18cb4e622790a3", size = 188890, upload-time = "2026-07-10T06:32:39.545Z" }, + { url = "https://files.pythonhosted.org/packages/5a/32/126bbc844be5afb3613fd43211dac10a9645f4cf39741d04acaa2ec7030c/websockets-16.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7143aa09a67e1c013be44e81a88dfe90fc6244198ab86c7edd064152cf619805", size = 186583, upload-time = "2026-07-10T06:32:41.038Z" }, + { url = "https://files.pythonhosted.org/packages/22/b9/0b5db9cbcf6e4970db4496893244a8d92e07f71a8ef27cf34b08aa02fef1/websockets-16.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:7acb811fad08e611755800d1560e395c67e11a6bd563598ea6abb319afb86938", size = 187353, upload-time = "2026-07-10T06:32:42.501Z" }, + { url = "https://files.pythonhosted.org/packages/99/2e/254b2131a10d831b76e2c18dfe7add9729c6292c674a8085bf8de01ad151/websockets-16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c5cf88e3faa2f7931bc6baeee7599c97656a3f6ac7f831f4fccba233e141783a", size = 187784, upload-time = "2026-07-10T06:32:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/21/dc/e7288aa8e3ac5a88a0924619984d663c1abf2a87d0ea98290c66fdaee0ec/websockets-16.1-cp314-cp314t-win32.whl", hash = "sha256:589f8842521c8307684ce0b40ce4ad70c5e0aa46484c6f1225a94ef4b8970341", size = 179947, upload-time = "2026-07-10T06:32:45.495Z" }, + { url = "https://files.pythonhosted.org/packages/d3/de/37edf1260ff0fbbd2f82433489c4cfbe799ac2ff21355331609879329fe6/websockets-16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c0e0857c30bbbc2bb5c30687508f0b7ec19aa026cd9f2ff8424d0fee42dcc07", size = 180291, upload-time = "2026-07-10T06:32:47.119Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f4/84ef884775bbe77c46cce79bc7d705ea3bc6574cc00acf81af89754c077d/websockets-16.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7289d899c79e763e6221c8dcb8959361cb43274418538d7c7ad16a43b01d12f9", size = 177387, upload-time = "2026-07-10T06:32:48.574Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d9/6831ec6f65e1eeac770375f4f4b604f23df9bafaa1b47004bc5f9488d513/websockets-16.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e22e9e3719f5131bd62da4db63c8da63eb8c91cc99e16c1cbd122f130e1ae07a", size = 177663, upload-time = "2026-07-10T06:32:50.043Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/21d4922fa7fe855813a8b38f181a0ecf02a586e16c1f095fd05471f78cc2/websockets-16.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:83bdabafef431247e6b11a9aab8a0893fd8e82e1ed95b32e0373625b03ffce4a", size = 178501, upload-time = "2026-07-10T06:32:51.439Z" }, + { url = "https://files.pythonhosted.org/packages/91/87/7a0320df854dacd09507ca972cb04a4dc5aae279583cc5b80ad5f5819533/websockets-16.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b8d13ceabc5c60995f201b5211d76876e17e68706ebf5d3bc666b32eefff1a6", size = 179397, upload-time = "2026-07-10T06:32:52.892Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/0da1eb8c8da2ace7b578c8523d32618af85e62a9ebad56051d4a14a38a1c/websockets-16.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81495f9c0085361c582efbc3207fb877174cfe03370f17d9cd70624404aa526f", size = 180546, upload-time = "2026-07-10T06:32:54.619Z" }, + { url = "https://files.pythonhosted.org/packages/66/58/bd83247f39ddc26ffc2c24eb05087a3b749e00cb4509fc6d19daa23c8495/websockets-16.1-py3-none-any.whl", hash = "sha256:c5149dfe490ec7e5ee5dbf624c642fb725f93a5575c7f00ab594ca9eddb8dd81", size = 174031, upload-time = "2026-07-10T06:32:56.079Z" }, ] [[package]]