From dea7d9a00f8c9d37a155a21c13070eba7309310d Mon Sep 17 00:00:00 2001 From: Imani Pelton Date: Thu, 7 May 2026 11:02:14 -0400 Subject: [PATCH 01/61] fix: correctly get project name for remote builds (#2681) Fixes #2598. CHARMCRAFT-690 --- charmcraft/application/commands/remote.py | 2 +- docs/release-notes/charmcraft-4.2.rst | 5 +++ tests/unit/commands/test_remote.py | 47 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/unit/commands/test_remote.py 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/docs/release-notes/charmcraft-4.2.rst b/docs/release-notes/charmcraft-4.2.rst index 0e28986e4..5a099a872 100644 --- a/docs/release-notes/charmcraft-4.2.rst +++ b/docs/release-notes/charmcraft-4.2.rst @@ -93,6 +93,11 @@ The following issues have been resolved in Charmcraft 4.2.1: - `#2620 `__ Multi-base shorthand notation fails on v4.2.0 +The following issues have been resolved in Charmcraft 4.2.2: + +- `#2598 `__ Remote build fails +when getting project name + Contributors ------------ 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() From a54f9ac075138cc6d7928825d0b5da9ff4bc083e Mon Sep 17 00:00:00 2001 From: JJ Coldiron Date: Tue, 2 Jun 2026 21:44:33 +0200 Subject: [PATCH 02/61] docs: add common remote build pages (#2702) Add the common remote build how-to and reference pages. --------- Signed-off-by: JJ Coldiron --- docs/howto/build-remotely.rst | 5 +++++ docs/howto/index.rst | 36 +++++++++++++++++--------------- docs/reference/index.rst | 1 + docs/reference/remote-builds.rst | 5 +++++ 4 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 docs/howto/build-remotely.rst create mode 100644 docs/reference/remote-builds.rst 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/index.rst b/docs/howto/index.rst index fca669b06..2f6d757d4 100644 --- a/docs/howto/index.rst +++ b/docs/howto/index.rst @@ -39,6 +39,7 @@ Publish charms and manage releases 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` @@ -60,20 +61,21 @@ migration guide for your charm: .. toctree:: - :hidden: - - manage-charmcraft - manage-charms - Manage 12-factor app charms - manage-extensions - manage-resources - manage-libraries - manage-parts - manage-the-current-charmhub-user - manage-names - manage-revisions - manage-channels - manage-tracks - manage-icons - Migrate plugins - Build + :hidden: + + manage-charmcraft + manage-charms + Manage 12-factor app charms + manage-extensions + manage-resources + manage-libraries + manage-parts + build-remotely + manage-the-current-charmhub-user + manage-names + manage-revisions + manage-channels + manage-tracks + manage-icons + Migrate plugins + Build diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 8b0dad22b..f2ccc3037 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -72,4 +72,5 @@ applications. 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 From b5334d9b2a81bc120cf47d176e9d08d9f2fca0b8 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 6 Jun 2026 08:49:13 +1200 Subject: [PATCH 03/61] docs: refer to inline charmcraft.yaml metadata in pack help (#2705) The `pack --help` overview instructed users to ensure a valid `metadata.yaml` exists. That is the legacy split-file format; modern (4.x) charms put metadata inline in `charmcraft.yaml`, so the stale guidance sent readers looking for a file that should not exist. --------- Signed-off-by: Tony Meyer Co-authored-by: Claude Opus 4.7 (1M context) --- charmcraft/application/commands/lifecycle.py | 8 ++++---- docs/release-notes/charmcraft-4.2.rst | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/charmcraft/application/commands/lifecycle.py b/charmcraft/application/commands/lifecycle.py index ac46268d1..ba418447d 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/docs/release-notes/charmcraft-4.2.rst b/docs/release-notes/charmcraft-4.2.rst index 5a099a872..29a0ccb51 100644 --- a/docs/release-notes/charmcraft-4.2.rst +++ b/docs/release-notes/charmcraft-4.2.rst @@ -92,6 +92,8 @@ 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: From 4506aa46c850a6d438ff2c71e2b227824b037c69 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:51:42 -0400 Subject: [PATCH 04/61] build(deps): update dependency django to v6 (#2737) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/tutorial/code/django/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 564f17bf700feb3b14a4e0f5fcd9a328688afdf9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 08:58:47 -0400 Subject: [PATCH 05/61] build(deps): update dependency fastapi to v0.138.0 (#2738) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/tutorial/code/fastapi/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/code/fastapi/requirements.txt b/docs/tutorial/code/fastapi/requirements.txt index dc22bedff..e58e8cf5a 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.138.0 psycopg2-binary==2.9.12 From b5c4d6c2605cd3f26170e390649f5dcf19e32142 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 23 Jun 2026 14:50:00 -0400 Subject: [PATCH 06/61] ci: Fix CI filter job and linting issues (#2740) --- .github/workflows/policy.yaml | 4 ++++ tests/unit/commands/test_lifecycle.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/policy.yaml b/.github/workflows/policy.yaml index ef184b823..b5c88f78f 100644 --- a/.github/workflows/policy.yaml +++ b/.github/workflows/policy.yaml @@ -13,6 +13,10 @@ jobs: outputs: source-files: ${{ steps.filter.outputs.source-files }} steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter with: diff --git a/tests/unit/commands/test_lifecycle.py b/tests/unit/commands/test_lifecycle.py index 5b15606a3..588d8db51 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() From 3fcdf8a9f82055aedeccc1d82a159a7e054290b2 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 23 Jun 2026 17:39:55 -0400 Subject: [PATCH 07/61] docs: add 4.3 release notes (#2728) This adds release notes for Charmcraft 4.3 so we can tag 4.3.0. This also creates (and excludes) blank release notes for Charmcraft 4.4. Requires #2727 --- docs/conf.py | 1 + docs/release-notes/charmcraft-4.3.rst | 157 ++++++++++++++++++++++++++ docs/release-notes/charmcraft-4.4.rst | 61 ++++++++++ docs/release-notes/index.rst | 2 + 4 files changed, 221 insertions(+) create mode 100644 docs/release-notes/charmcraft-4.3.rst create mode 100644 docs/release-notes/charmcraft-4.4.rst diff --git a/docs/conf.py b/docs/conf.py index f3ebd48a7..955f73300 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -194,6 +194,7 @@ # Excludes files or directories from processing exclude_patterns = [ + "release-notes/charmcraft-4.4.rst", "README.md", # Docs README "reuse", "common/craft-parts/explanation/lifecycle.rst", diff --git a/docs/release-notes/charmcraft-4.3.rst b/docs/release-notes/charmcraft-4.3.rst new file mode 100644 index 000000000..a19f0fb23 --- /dev/null +++ b/docs/release-notes/charmcraft-4.3.rst @@ -0,0 +1,157 @@ +.. 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. + +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: + +- `#2600 `__ + ``charmcraft pack -p`` and ``-o`` arguments did not work correctly together + + +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..318a8e37f --- /dev/null +++ b/docs/release-notes/charmcraft-4.4.rst @@ -0,0 +1,61 @@ +.. 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. + + +Fixed bugs and issues +--------------------- + +The following issues have been resolved in Charmcraft 4.4: + + +Known issues +------------ + +The following issues were reported and are scheduled to be fixed in upcoming +patch releases. + +See individual issue links for any mitigations. + + +Contributors +------------ + +We would like to express a big thank you to all the people who contributed to +this release. diff --git a/docs/release-notes/index.rst b/docs/release-notes/index.rst index f78115cfe..107f34e48 100644 --- a/docs/release-notes/index.rst +++ b/docs/release-notes/index.rst @@ -14,6 +14,7 @@ Current releases Charmcraft 4 ~~~~~~~~~~~~ +- :doc:`Charmcraft 4.3 ` - :doc:`Charmcraft 4.2 ` - :doc:`Charmcraft 4.1 ` - :doc:`Charmcraft 4.0 ` @@ -88,6 +89,7 @@ keeps pace with the OS's new releases and support lifecycle. :hidden: :maxdepth: 1 + Charmcraft 4.3 Charmcraft 4.2 Charmcraft 4.1 Charmcraft 4.0 From b93737a18bfbfabbec8439e34a2a3a6141fd33dd Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 23 Jun 2026 18:12:06 -0400 Subject: [PATCH 08/61] feat(charmlibs): warn of deprecation on all charmlibs commands (#2691) Warns about the deprecation of charmhub-hosted charmlibs in all charmlibs-related commands. --------- Signed-off-by: Alex Lowe Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: James Garner Co-authored-by: Imani Pelton Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- charmcraft/application/commands/store.py | 13 ++++++ .../commands/test_store_commands.py | 6 ++- tests/unit/commands/test_store.py | 45 ++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/charmcraft/application/commands/store.py b/charmcraft/application/commands/store.py index beaeaaa1f..47ebd1c5d 100644 --- a/charmcraft/application/commands/store.py +++ b/charmcraft/application/commands/store.py @@ -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): @@ -1255,6 +1263,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 +1358,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 +1525,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 +1677,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 +1786,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/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/unit/commands/test_store.py b/tests/unit/commands/test_store.py index 36135f9cc..34a37a8d7 100644 --- a/tests/unit/commands/test_store.py +++ b/tests/unit/commands/test_store.py @@ -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, ) @@ -162,7 +165,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 +200,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 +274,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 +408,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.") From 8a42aacf9d796e79ed987575d1d2120db14f8365 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 24 Jun 2026 09:39:57 -0400 Subject: [PATCH 09/61] docs: update intersphinx links (#2741) --- docs/conf.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 955f73300..7c466f1cd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -293,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), } From ef5d82950a135df8b4b0e6b2ffb45ee9ce1490a6 Mon Sep 17 00:00:00 2001 From: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com> Date: Fri, 26 Jun 2026 23:05:35 +0700 Subject: [PATCH 10/61] docs: add Canonical K8s instructions to web app charm how-tos (#2743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add Canonical K8s instructions to web app charm how-tos Migrate the MicroK8s-only steps in the manage-web-app-charms how-to guides to also cover Canonical K8s, matching the tabbed pattern used in the "Use a database with your 12-factor app charm" guide. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix link * Update docs/howto/manage-web-app-charms/use-web-app-charm.rst Co-authored-by: Ali UĞUR <39213991+alithethird@users.noreply.github.com> Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com> --------- Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ali UĞUR <39213991+alithethird@users.noreply.github.com> --- .../integrate-web-app-charm.rst | 24 ++- .../use-web-app-charm.rst | 140 +++++++++++++++--- 2 files changed, 138 insertions(+), 26 deletions(-) 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..35cd431c1 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 @@ -244,24 +244,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:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash - microk8s.kubectl get all -n + microk8s.kubectl get all -n + + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash -This command outputs a list of all the MicroK8s resources in the web app's + 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 + + microk8s kubectl logs -n + + .. tab-item:: Canonical K8s + :sync: canonical-k8s -.. code:: + .. code-block:: bash - microk8s kubectl logs -n + 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 @@ -272,48 +296,122 @@ name of the web app with the ``-c`` option. .. tab-item:: Django :sync: django - .. code-block:: bash + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c django-app - microk8s kubectl logs -n -c django-app + .. tab-item:: Canonical K8s + :sync: canonical-k8s + + .. code-block:: bash + + sudo k8s kubectl logs -n -c django-app .. tab-item:: Express :sync: express - .. code-block:: bash + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app - 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 - .. code-block:: bash + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s - microk8s kubectl logs -n -c app + .. code-block:: bash + + sudo k8s kubectl logs -n -c app .. tab-item:: Flask :sync: flask - .. code-block:: bash + .. tab-set:: + + .. tab-item:: MicroK8s + :sync: microk8s + + .. code-block:: bash + + microk8s kubectl logs -n -c flask-app + + .. tab-item:: Canonical K8s + :sync: canonical-k8s - microk8s kubectl logs -n -c flask-app + .. code-block:: bash + + sudo k8s kubectl logs -n -c flask-app .. tab-item:: Go :sync: go - .. code-block:: bash + .. 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 .. tab-item:: Spring Boot :sync: spring-boot - .. code-block:: bash + .. 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 ~~~~~~~~~~~~~~~ From ad3588c65653ecd046a45581534c8ddcabb64928 Mon Sep 17 00:00:00 2001 From: Erin Conley Date: Sat, 27 Jun 2026 11:12:04 +0200 Subject: [PATCH 11/61] fix(docs): auto-sync framework versions to 12-factor tutorials (#2721) * build(deps): update dependency django to v5.2.15 * docs: update django tutorial with version * docs: one way to sync the docs and code * fix: trailing whitespace * docs: script in conf.py to extract django version * docs: make linter happy * fix(docs): revert phrasing change in tutorial * fix: update install spread step of spread-docs.yaml * chore(copilot): add 24.04 base support to Django framework * fix: workflow path in spread-docs.yaml * chore(copilot): add 24.04 base support to Flask framework * fix: name of charms in task.yaml * fix: CHARMCRAFTCRAFT * fix: name of charms in task.yaml * chore: revert all spread test changes * chore: reorder the imports in conf.py * docs: update name of django version global var * chore: incorporate feedback from @medubelko * chore: update placeholder to something less concerning * chore: convert code snippet to function * chore(docs): update spread test and docs for updatelogfile action howto * fix(docs): revert spread test changes * chore(docs): update flask and fastapi versions * docs: small refactoring --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael DuBelko --- docs/conf.py | 19 +++++++++++++++++++ docs/tutorial/kubernetes-charm-django.rst | 2 +- docs/tutorial/kubernetes-charm-fastapi.rst | 2 +- docs/tutorial/kubernetes-charm-flask.rst | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7c466f1cd..5ca5aedb8 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 @@ -344,3 +345,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/tutorial/kubernetes-charm-django.rst b/docs/tutorial/kubernetes-charm-django.rst index 2c64d4ec7..adba3d59b 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 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 From 6aca1903da7aaeb458ac3efb995d5fdcf598b78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20U=C4=9EUR?= <39213991+alithethird@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:20:27 +0300 Subject: [PATCH 12/61] Feat: extension dispatch for 12-factor extensions. (#2722) --- charmcraft/application/commands/init.py | 4 + charmcraft/application/main.py | 16 +- charmcraft/const.py | 23 +- charmcraft/extensions/__init__.py | 28 +- charmcraft/extensions/app.py | 211 +++++++- charmcraft/extensions/extension.py | 102 ++-- charmcraft/models/project.py | 15 +- .../init-django-framework-26.04/.gitignore.j2 | 9 + .../charmcraft.yaml.j2 | 81 ++++ .../pyproject.toml.j2 | 70 +++ .../src/charm.py.j2 | 30 ++ .../init-django-framework-26.04/tox.ini.j2 | 80 ++++ .../init-django-framework/charmcraft.yaml.j2 | 4 +- .../.gitignore.j2 | 9 + .../charmcraft.yaml.j2 | 80 ++++ .../pyproject.toml.j2 | 70 +++ .../src/charm.py.j2 | 30 ++ .../init-expressjs-framework-26.04/tox.ini.j2 | 80 ++++ .../charmcraft.yaml.j2 | 2 +- .../.gitignore.j2 | 9 + .../charmcraft.yaml.j2 | 81 ++++ .../pyproject.toml.j2 | 70 +++ .../src/charm.py.j2 | 30 ++ .../init-fastapi-framework-26.04/tox.ini.j2 | 80 ++++ .../init-fastapi-framework/charmcraft.yaml.j2 | 4 +- .../init-flask-framework-26.04/.gitignore.j2 | 9 + .../charmcraft.yaml.j2 | 80 ++++ .../pyproject.toml.j2 | 70 +++ .../src/charm.py.j2 | 30 ++ .../init-flask-framework-26.04/tox.ini.j2 | 80 ++++ .../init-flask-framework/charmcraft.yaml.j2 | 2 +- .../init-go-framework-26.04/.gitignore.j2 | 9 + .../charmcraft.yaml.j2 | 80 ++++ .../init-go-framework-26.04/pyproject.toml.j2 | 70 +++ .../init-go-framework-26.04/src/charm.py.j2 | 30 ++ .../init-go-framework-26.04/tox.ini.j2 | 80 ++++ .../init-go-framework/charmcraft.yaml.j2 | 4 +- .../.gitignore.j2 | 9 + .../charmcraft.yaml.j2 | 81 ++++ .../pyproject.toml.j2 | 70 +++ .../src/charm.py.j2 | 30 ++ .../tox.ini.j2 | 80 ++++ .../charmcraft.yaml.j2 | 4 +- tests/extensions/test_app.py | 449 ++++++++++++++++-- tests/extensions/test_registry.py | 96 ++++ .../multibase-charm-plugin/charmcraft.yaml | 3 - .../errors.json | 4 +- .../resolute-charm-plugin/errors.json | 4 +- .../platforms-noble-charm/charmcraft.yaml | 12 + .../platforms-noble-charm/expected.yaml | 16 + .../charmcraft.yaml | 2 +- .../expected.yaml | 2 +- tests/unit/models/test_project.py | 53 ++- 53 files changed, 2528 insertions(+), 149 deletions(-) create mode 100644 charmcraft/templates/init-django-framework-26.04/.gitignore.j2 create mode 100644 charmcraft/templates/init-django-framework-26.04/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 create mode 100755 charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 create mode 100644 charmcraft/templates/init-django-framework-26.04/tox.ini.j2 create mode 100644 charmcraft/templates/init-expressjs-framework-26.04/.gitignore.j2 create mode 100644 charmcraft/templates/init-expressjs-framework-26.04/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 create mode 100755 charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 create mode 100644 charmcraft/templates/init-expressjs-framework-26.04/tox.ini.j2 create mode 100644 charmcraft/templates/init-fastapi-framework-26.04/.gitignore.j2 create mode 100644 charmcraft/templates/init-fastapi-framework-26.04/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 create mode 100755 charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 create mode 100644 charmcraft/templates/init-fastapi-framework-26.04/tox.ini.j2 create mode 100644 charmcraft/templates/init-flask-framework-26.04/.gitignore.j2 create mode 100644 charmcraft/templates/init-flask-framework-26.04/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 create mode 100755 charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 create mode 100644 charmcraft/templates/init-flask-framework-26.04/tox.ini.j2 create mode 100644 charmcraft/templates/init-go-framework-26.04/.gitignore.j2 create mode 100644 charmcraft/templates/init-go-framework-26.04/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 create mode 100755 charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 create mode 100644 charmcraft/templates/init-go-framework-26.04/tox.ini.j2 create mode 100644 charmcraft/templates/init-spring-boot-framework-26.04/.gitignore.j2 create mode 100644 charmcraft/templates/init-spring-boot-framework-26.04/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 create mode 100755 charmcraft/templates/init-spring-boot-framework-26.04/src/charm.py.j2 create mode 100644 charmcraft/templates/init-spring-boot-framework-26.04/tox.ini.j2 create mode 100644 tests/integration/sample-charms/platforms-noble-charm/charmcraft.yaml create mode 100644 tests/integration/sample-charms/platforms-noble-charm/expected.yaml rename tests/integration/sample-charms/{platforms-resolute-reactive => platforms-noble-reactive}/charmcraft.yaml (91%) rename tests/integration/sample-charms/{platforms-resolute-reactive => platforms-noble-reactive}/expected.yaml (93%) 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/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..20dd29562 100644 --- a/charmcraft/const.py +++ b/charmcraft/const.py @@ -68,15 +68,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/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..6b45020fc 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -23,7 +23,7 @@ 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": { @@ -73,7 +73,59 @@ COS_SUBDIRS = {"grafana_dashboards", "loki_alert_rules", "prometheus_alert_rules"} -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 = [ @@ -101,9 +153,9 @@ def get_supported_bases() -> list[tuple[str, str]]: @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 = { @@ -325,6 +377,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 +456,7 @@ def get_image_name(self) -> str: } -class FlaskFramework(_AppBase): +class FlaskFrameworkV1(_AppBase): """Extension for 12-factor Flask applications.""" framework = "flask" @@ -393,14 +500,18 @@ 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 + +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" @@ -436,14 +547,19 @@ class DjangoFramework(_AppBase): }, } - @staticmethod - @override - def is_experimental(base: tuple[str, ...] | None) -> bool: # noqa: ARG004 - """Check if the extension is in an experimental state.""" - return False +class DjangoFrameworkV2(_AppBaseV2): + """Extension v2 for 12-factor Django applications.""" + + framework = "django" + actions = {**DjangoFrameworkV1.actions} + options = DjangoFrameworkV1.options -class GoFramework(_AppBase): + +DjangoFrameworkFactory = _FrameworkFactory(DjangoFrameworkV1, DjangoFrameworkV2) + + +class GoFrameworkV1(_AppBase): """Extension for 12-factor Go applications.""" framework = "go" @@ -470,7 +586,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 +641,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 +678,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 +735,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 +744,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/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..85441d1bd --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,70 @@ +# 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", +] + + +# 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..359b47307 --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/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..bd15d819c --- /dev/null +++ b/charmcraft/templates/init-django-framework-26.04/tox.ini.j2 @@ -0,0 +1,80 @@ +# 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 +deps = + ruff + codespell +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration 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-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..85441d1bd --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,70 @@ +# 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", +] + + +# 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..798b68376 --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/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..bd15d819c --- /dev/null +++ b/charmcraft/templates/init-expressjs-framework-26.04/tox.ini.j2 @@ -0,0 +1,80 @@ +# 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 +deps = + ruff + codespell +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration 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-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..85441d1bd --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,70 @@ +# 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", +] + + +# 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..84f9fa77a --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/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..bd15d819c --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework-26.04/tox.ini.j2 @@ -0,0 +1,80 @@ +# 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 +deps = + ruff + codespell +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration 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-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..85441d1bd --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,70 @@ +# 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", +] + + +# 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..94cb3f33f --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/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..bd15d819c --- /dev/null +++ b/charmcraft/templates/init-flask-framework-26.04/tox.ini.j2 @@ -0,0 +1,80 @@ +# 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 +deps = + ruff + codespell +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration 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-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..85441d1bd --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,70 @@ +# 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", +] + + +# 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..c32223b7e --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/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..bd15d819c --- /dev/null +++ b/charmcraft/templates/init-go-framework-26.04/tox.ini.j2 @@ -0,0 +1,80 @@ +# 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 +deps = + ruff + codespell +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration 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-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..46991a3ea --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 @@ -0,0 +1,70 @@ +# 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", +] + + +# 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..cb3178463 --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/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..bd15d819c --- /dev/null +++ b/charmcraft/templates/init-spring-boot-framework-26.04/tox.ini.j2 @@ -0,0 +1,80 @@ +# 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 +deps = + ruff + codespell +commands = + codespell {tox_root} + ruff check {[vars]all_path} + ruff format --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration 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/tests/extensions/test_app.py b/tests/extensions/test_app.py index 6854ff5eb..6a4b1ac08 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", "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 = [ @@ -694,7 +1087,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 +1106,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 +1134,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/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/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()], From fb6f09d141122b957327d96fc13358e60fab9e34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:38:36 -0400 Subject: [PATCH 13/61] build(deps): update bugfixes (#2746) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- docs/tutorial/code/fastapi/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da63a53ca..466c8deb0 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.20" hooks: # Run the linter - id: ruff diff --git a/docs/tutorial/code/fastapi/requirements.txt b/docs/tutorial/code/fastapi/requirements.txt index e58e8cf5a..4219ecb41 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.138.0 +fastapi[standard]==0.138.1 psycopg2-binary==2.9.12 From c559e7ff5cf3e38df257c3d526a6719eff48911f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:20:21 -0400 Subject: [PATCH 14/61] build(deps): update actions/checkout action to v7 (#2747) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/check-renovate.yaml | 2 +- .github/workflows/policy.yaml | 2 +- .github/workflows/qa.yaml | 2 +- .github/workflows/release-publish.yaml | 2 +- .github/workflows/schema.yaml | 4 ++-- .github/workflows/spread-docs.yaml | 8 ++++---- .github/workflows/spread-manual.yaml | 4 ++-- .github/workflows/spread.yaml | 8 ++++---- .github/workflows/tests.yaml | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) 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/policy.yaml b/.github/workflows/policy.yaml index b5c88f78f..bca46a475 100644 --- a/.github/workflows/policy.yaml +++ b/.github/workflows/policy.yaml @@ -14,7 +14,7 @@ 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 diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index a2dc0416c..787d2d076 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -17,7 +17,7 @@ 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 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..e369b2400 100644 --- a/.github/workflows/spread-docs.yaml +++ b/.github/workflows/spread-docs.yaml @@ -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 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..87d943d53 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -49,7 +49,7 @@ jobs: runs-on: [self-hosted, amd64] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Build snap @@ -67,7 +67,7 @@ jobs: systems: ${{ steps.select.outputs.systems }} steps: - name: Checkout charmcraft - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Find spread systems id: select run: | @@ -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 @@ -117,7 +117,7 @@ jobs: 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 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3fb59052b..0e4d903c5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -54,7 +54,7 @@ jobs: runs-on: [self-hosted, amd64] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Build snap From 8ef2697840d8b83ef8c0ac84c80d9b6e5c1e1250 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 30 Jun 2026 09:42:10 -0400 Subject: [PATCH 15/61] test: add regression test for issue #2361 (#2735) --- tests/unit/commands/test_lifecycle.py | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/unit/commands/test_lifecycle.py b/tests/unit/commands/test_lifecycle.py index 588d8db51..a211fac7e 100644 --- a/tests/unit/commands/test_lifecycle.py +++ b/tests/unit/commands/test_lifecycle.py @@ -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" From f1df851b0cabaa5f63586bdeea1991c80b96ab33 Mon Sep 17 00:00:00 2001 From: javierdelapuente Date: Tue, 30 Jun 2026 16:20:36 +0200 Subject: [PATCH 16/61] feat: paas-config.yaml validation (#2596) The 12-factor tooling accepts a configuration file `paas-config.yaml` that is read by the `paas-charm` library. See https://canonical-12-factor-app-support.readthedocs-hosted.com/latest/reference/paas-config/ This file is written in `yaml` format, and currently it contains functionality about Prometheus scraping configuration and [logging formatting in the frameworks](https://canonical-12-factor-app-support.readthedocs-hosted.com/latest/reference/paas-config-structured-logging/). If the file is not correct, because it is not valid `yaml` or because the keys/values are wrong, the application will get into error state, as it is invalid and the file has to be fixed and the charm packed again. To improve the feedback loop, this PR will make the `charmcraft pack` fail if the `paas-config.yaml` is not correct `yaml`. It will also fail if the `framework_logging_format` is set to `json` for any of the frameworks where it is not supported (only Gunicorn and Uvicorn based frameworks support this option, as the other frameworks can be easily configured in the charmed application). --- charmcraft/extensions/app.py | 31 ++++++++++++++++++++++++++++ tests/extensions/test_app.py | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/charmcraft/extensions/app.py b/charmcraft/extensions/app.py index 6b45020fc..96bce88c2 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -20,6 +20,7 @@ from pathlib import Path from typing import Any +import yaml from overrides import override from ..errors import ExtensionError @@ -71,6 +72,8 @@ } COS_SUBDIRS = {"grafana_dashboards", "loki_alert_rules", "prometheus_alert_rules"} +PAAS_CONFIG_FILE = "paas-config.yaml" +JSON_LOGGING_SUPPORTED_FRAMEWORKS = {"fastapi", "flask", "django"} class _FrameworkFactory: @@ -246,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.""" diff --git a/tests/extensions/test_app.py b/tests/extensions/test_app.py index 6a4b1ac08..f2c315938 100644 --- a/tests/extensions/test_app.py +++ b/tests/extensions/test_app.py @@ -1080,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"), [ From 40c765de71b716fd8b5be7a158cff3c7fd808571 Mon Sep 17 00:00:00 2001 From: JJ Coldiron Date: Tue, 30 Jun 2026 17:11:29 -0700 Subject: [PATCH 17/61] docs: configure latest docs for canonical.com proxy (#2717) --- docs/_static/js/overwrite-links.js | 38 ++++++++++++++++++++++++++++++ docs/conf.py | 19 ++++++++------- docs/howto/manage-charmcraft.rst | 2 +- 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 docs/_static/js/overwrite-links.js 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 5ca5aedb8..ea74be3a3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,7 +42,7 @@ 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 @@ -88,7 +88,7 @@ # } # Project slug; see https://meta.discourse.org/t/what-is-category-slug/87897 -# slug = '' +slug = "juju/docs/charmcraft" ######################### @@ -96,19 +96,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/", ] @@ -253,12 +253,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 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': From 9ce27834301fe00f0f48ba5c86a30a5b573be825 Mon Sep 17 00:00:00 2001 From: JJ Coldiron Date: Tue, 30 Jun 2026 17:11:42 -0700 Subject: [PATCH 18/61] docs: configure 4.3 docs for canonical.com proxy (#2745) --- docs/_static/js/overwrite-links.js | 38 ++++++++++++++++++++++++++++++ docs/conf.py | 36 ++++++++++++++-------------- 2 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 docs/_static/js/overwrite-links.js 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 7c466f1cd..d495a1033 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,7 @@ # 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 @@ -41,7 +41,7 @@ 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/", ] @@ -118,6 +118,16 @@ html_static_path = ["_static"] templates_path = ["_templates"] +# Files for the cookie banner +html_css_files = [ + "css/cookie-banner.css", +] + +html_js_files = [ + "js/bundle.js", + "js/overwrite-links.js", +] + ############# # Redirects # @@ -250,16 +260,6 @@ "common/craft-application/reference/strict-platform-names.rst", ] -# Adds custom CSS files, located under 'html_static_path' -html_css_files = [ - 'css/cookie-banner.css' -] - -# Adds custom JavaScript files, located under 'html_static_path' -html_js_files = [ - 'js/bundle.js', -] - # Specifies a reST snippet to be appended to each .rst file rst_epilog = """ .. include:: /reuse/links.txt From 142841824260c21752909d4992d04de38b55a263 Mon Sep 17 00:00:00 2001 From: Erin Conley Date: Thu, 2 Jul 2026 22:08:32 +0200 Subject: [PATCH 19/61] docs: Update 12-factor Spread tests (#2732) * build(deps): update dependency django to v5.2.15 * docs: update django tutorial with version * docs: one way to sync the docs and code * fix: trailing whitespace * docs: script in conf.py to extract django version * docs: make linter happy * fix(docs): revert phrasing change in tutorial * fix: update install spread step of spread-docs.yaml * chore(copilot): add 24.04 base support to Django framework * fix: workflow path in spread-docs.yaml * chore(copilot): add 24.04 base support to Flask framework * fix: name of charms in task.yaml * fix: CHARMCRAFTCRAFT * fix: name of charms in task.yaml * chore(docs): remove auto-sync aspects * chore: revert auto-sync feature * fix: charm name in custom-action-updatelogfile/task.yaml * chore(docs): update spread test for updatelogfile action * chore(docs): update spread test and howto for updatelogfile * chore(docs): update spread test and howto for updatelogfile * fix: name of charm in updatelogfile test * chore: revert changes to charmcraft/extensions/app.py * chore: revert the newline change too --------- Signed-off-by: Alex Lowe Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Alex Lowe --- .github/workflows/spread-docs.yaml | 4 ++-- docs/howto/code/custom-action-clearsession/task.yaml | 4 ++-- docs/howto/code/custom-action-updatelogfile/task.yaml | 10 ++++++++-- docs/howto/code/flask-async/task.yaml | 2 +- .../manage-web-app-charms/configure-web-app-charm.rst | 3 ++- docs/tutorial/code/django/task.yaml | 6 +++--- docs/tutorial/code/fastapi/task.yaml | 2 +- docs/tutorial/code/flask/task.yaml | 6 +++--- 8 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/spread-docs.yaml b/.github/workflows/spread-docs.yaml index e369b2400..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/** @@ -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/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/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/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 00aa0dab1..23288d396 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -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/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] From 99d7be3bf68982214c38d78e78f6596a963c622b Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 3 Jul 2026 12:42:21 +1200 Subject: [PATCH 20/61] docs: clarify config-option path is for user secrets, add use-case map (#2712) --------- Signed-off-by: Tony Meyer Co-authored-by: Michael DuBelko Co-authored-by: Michael DuBelko --- docs/howto/manage-charms.rst | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/howto/manage-charms.rst b/docs/howto/manage-charms.rst index be210e747..625bb5814 100644 --- a/docs/howto/manage-charms.rst +++ b/docs/howto/manage-charms.rst @@ -498,9 +498,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` From a520d616e80ca521e8a47e8b9145a6d361bda3f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:04:06 -0400 Subject: [PATCH 21/61] build(deps): update dependency fastapi to v0.139.0 (#2757) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/tutorial/code/fastapi/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/code/fastapi/requirements.txt b/docs/tutorial/code/fastapi/requirements.txt index 4219ecb41..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.138.1 +fastapi[standard]==0.139.0 psycopg2-binary==2.9.12 From 0389aeb442e6d2e6c2eee02020b5e0665ffece3a Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Wed, 8 Jul 2026 07:23:50 +0800 Subject: [PATCH 22/61] docs: improve charm naming docs, with links to detailed guidance (#2748) --------- Signed-off-by: Michael DuBelko Co-authored-by: Michael DuBelko --- docs/howto/manage-charms.rst | 51 +++++++++---------- docs/reference/files/charmcraft-yaml-file.rst | 21 ++------ 2 files changed, 29 insertions(+), 43 deletions(-) diff --git a/docs/howto/manage-charms.rst b/docs/howto/manage-charms.rst index be210e747..fb0f64c36 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. + +```` 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. - mkdir my-flask-app-k8s - cd my-flask-app-k8s/ - charmcraft init --profile flask-framework +.. 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` 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`` From b8f89fea48563bfd65cb19920c1a28142afc567a Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 8 Jul 2026 16:54:21 -0400 Subject: [PATCH 23/61] ci: replace self-hosted runner with ubuntu-24.04 (#2750) --- .github/workflows/spread.yaml | 2 +- .github/workflows/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 87d943d53..923abf5b1 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -46,7 +46,7 @@ on: jobs: snap-build: - runs-on: [self-hosted, amd64] + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v7 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0e4d903c5..e9af69fdf 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -51,7 +51,7 @@ on: jobs: snap-build: - runs-on: [self-hosted, amd64] + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v7 From eeca3f79bd785a67adb5eed1b90683e492210dff Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 9 Jul 2026 09:52:21 -0400 Subject: [PATCH 24/61] build: update craft-providers to >=3.7.1 for 25.10 spread tests (#2764) Updates craft-providers to >=3.7.1 to resolve Ubuntu 25.10 spread test failures. Signed-off-by: Alex Lowe --- pyproject.toml | 2 +- uv.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a8acd8d55..f6d4586c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dependencies = [ "craft-cli~=3.4", "craft-grammar>=2.0.0", "craft-parts~=2.20", - "craft-providers>=3.4.0,<4.0", + "craft-providers>=3.7.1,<4.0", "craft-platforms~=0.10", "craft-store~=3.2", "distro>=1.7.0", diff --git a/uv.lock b/uv.lock index 6d4ffc57d..558255dcc 100644 --- a/uv.lock +++ b/uv.lock @@ -1017,7 +1017,7 @@ wheels = [ [[package]] name = "craft-providers" -version = "3.6.0" +version = "3.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -1027,9 +1027,9 @@ 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]] From 3a73f062a04c539bf6feaa8760622924d9a9182a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20U=C4=9EUR?= <39213991+alithethird@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:58:59 +0300 Subject: [PATCH 25/61] feat: add support for Ubuntu 24.04 in supported bases (#2766) --- charmcraft/extensions/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charmcraft/extensions/app.py b/charmcraft/extensions/app.py index ff15be707..27653e729 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -97,7 +97,7 @@ 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 From cededd41e757771aff14b1649e01f120f0e1ce86 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 9 Jul 2026 13:37:35 -0400 Subject: [PATCH 26/61] chore: fix bug and release 4.3.1 (#2767) The bug was in craft-parts so I've updated the patch release in uv.lock. --------- Signed-off-by: Alex Lowe Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/release-notes/charmcraft-4.3.rst | 7 ++++++- uv.lock | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/charmcraft-4.3.rst b/docs/release-notes/charmcraft-4.3.rst index a19f0fb23..0b49661e9 100644 --- a/docs/release-notes/charmcraft-4.3.rst +++ b/docs/release-notes/charmcraft-4.3.rst @@ -111,11 +111,16 @@ Charmcraft 4.3 includes the following new and updated documentation: Fixed bugs and issues --------------------- -The following issues have been resolved in Charmcraft 4.3: +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 ------------ diff --git a/uv.lock b/uv.lock index 6d4ffc57d..f5937818c 100644 --- a/uv.lock +++ b/uv.lock @@ -983,7 +983,7 @@ wheels = [ [[package]] name = "craft-parts" -version = "2.34.0" +version = "2.34.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml" }, @@ -996,9 +996,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/a0/be/c6ad74c8f947da14490a65e52e516776b9ba104e29754a5f92961c1c74a8/craft_parts-2.34.1.tar.gz", hash = "sha256:24addd15e9301fad982c980643b1570c11c6d341ff0aafacb1aac607db6707cc", size = 1018369, upload-time = "2026-07-09T15:54:12.695Z" } 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/5a/aa/1368d843bd8b5d3da4ed4f5c2dfb7a9384159c1601f648de773f7ccfe676/craft_parts-2.34.1-py3-none-any.whl", hash = "sha256:72633268c953994b5a81fb757b01c28272897198a821cbbdab4ef001546c64cf", size = 560744, upload-time = "2026-07-09T15:54:10.526Z" }, ] [[package]] From e156d35303413a4e1e5c8f503fbe3d8e2177b624 Mon Sep 17 00:00:00 2001 From: "Alex M. Lowe" Date: Thu, 9 Jul 2026 13:56:23 -0400 Subject: [PATCH 27/61] test: fix test for flask framework factory --- tests/extensions/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/extensions/test_app.py b/tests/extensions/test_app.py index f2c315938..5ee27dd1b 100644 --- a/tests/extensions/test_app.py +++ b/tests/extensions/test_app.py @@ -803,7 +803,7 @@ def test_v2_extension_experimental_gating_passes_with_env(monkeypatch, tmp_path) 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", "26.04")] + assert bases == [("ubuntu", "22.04"), ("ubuntu", "24.04"), ("ubuntu", "26.04")] assert len(bases) == len(set(bases)), "Supported bases should not have duplicates" From 442302373ded8a6847534db427c7d2488d82c072 Mon Sep 17 00:00:00 2001 From: "Alex M. Lowe" Date: Thu, 9 Jul 2026 14:02:54 -0400 Subject: [PATCH 28/61] fix: remove conf.py dupes --- docs/conf.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 78f621419..250ca0eb1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -119,16 +119,6 @@ html_static_path = ["_static"] templates_path = ["_templates"] -# Files for the cookie banner -html_css_files = [ - "css/cookie-banner.css", -] - -html_js_files = [ - "js/bundle.js", - "js/overwrite-links.js", -] - ############# # Redirects # From 1d4807fe24e2684cac3556e40d66394c81fc8e2c Mon Sep 17 00:00:00 2001 From: Erin Conley Date: Thu, 9 Jul 2026 22:40:31 +0200 Subject: [PATCH 29/61] chore: add base support for Flask and Django extensions (#2756) --------- Signed-off-by: Erin Conley Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- charmcraft/extensions/app.py | 18 ++++++++++++++++++ docs/tutorial/code/django/task.yaml | 2 +- tests/extensions/test_app.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/charmcraft/extensions/app.py b/charmcraft/extensions/app.py index 96bce88c2..4e1c60371 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -531,6 +531,12 @@ class FlaskFrameworkV1(_AppBase): }, } + @staticmethod + @override + 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.""" @@ -578,6 +584,18 @@ class DjangoFrameworkV1(_AppBase): }, } + @staticmethod + @override + 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 DjangoFrameworkV2(_AppBaseV2): """Extension v2 for 12-factor Django applications.""" diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 23288d396..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 diff --git a/tests/extensions/test_app.py b/tests/extensions/test_app.py index f2c315938..5ee27dd1b 100644 --- a/tests/extensions/test_app.py +++ b/tests/extensions/test_app.py @@ -803,7 +803,7 @@ def test_v2_extension_experimental_gating_passes_with_env(monkeypatch, tmp_path) 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", "26.04")] + assert bases == [("ubuntu", "22.04"), ("ubuntu", "24.04"), ("ubuntu", "26.04")] assert len(bases) == len(set(bases)), "Supported bases should not have duplicates" From 3eaf69bacd1ff2483ed85fb7e1963f7a37c3f20e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:04:27 -0400 Subject: [PATCH 30/61] build(deps): lock file maintenance (#2758) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- uv.lock | 1420 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 755 insertions(+), 665 deletions(-) diff --git a/uv.lock b/uv.lock index b0c71af5a..af548234f 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]] @@ -531,7 +558,7 @@ requires-dist = [ { 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-providers", specifier = ">=3.7.1,<4.0" }, { name = "craft-store", specifier = "~=3.2" }, { name = "distro", specifier = ">=1.7.0" }, { name = "docker", specifier = ">=7.0.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.1" +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/a0/be/c6ad74c8f947da14490a65e52e516776b9ba104e29754a5f92961c1c74a8/craft_parts-2.34.1.tar.gz", hash = "sha256:24addd15e9301fad982c980643b1570c11c6d341ff0aafacb1aac607db6707cc", size = 1018369, upload-time = "2026-07-09T15:54:12.695Z" } +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/5a/aa/1368d843bd8b5d3da4ed4f5c2dfb7a9384159c1601f648de773f7ccfe676/craft_parts-2.34.1-py3-none-any.whl", hash = "sha256:72633268c953994b5a81fb757b01c28272897198a821cbbdab4ef001546c64cf", size = 560744, upload-time = "2026-07-09T15:54:10.526Z" }, + { 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]] @@ -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]] @@ -1240,7 +1234,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { 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')" }, + { 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')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -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]] From 6e775f4a882131a90fab7767fa0ca32078492b18 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 14 Jul 2026 11:07:28 -0400 Subject: [PATCH 31/61] fix(tests/spread): fix reactive spread tests (#2763) This PR fixes the failing reactive spread tests by making several structural and configuration changes: 1. **Test Splitting**: The single `reactive` test has been split into two distinct tests to avoid complex conditionals and cross-talk: - `reactive-legacy`: Runs on Ubuntu 20.04 and 22.04, testing the `2.x/stable` and `stable` charm channels. It dynamically sets its base channel to match the host system's base. - `reactive-resolute`: A standalone test specifically for Ubuntu 26.04, configured to strictly test the `charm/latest/stable` channel. 2. **Dependency Resolution Fix**: Explicitly adds `--binary-wheels` to the `charm-build` arguments in `charmcraft.yaml`. This prevents `charm-build` from defaulting to source builds (which fail due to PEP-517 isolated environments downloading newer `setuptools` versions that lack `pkg_resources`). Using binary wheels avoids these source build failures and speeds up the tests. 3. **Cleanup**: Removes `--upgrade-buildvenv-core-deps` and `--ignore-requires-python` flags from all `charmcraft.yaml` templates, as they were contributing to dependency resolution failures on older versions and are no longer necessary with binary wheels. --- .../reactivecharm/charmcraft.yaml | 5 +- .../reactivecharm/config.yaml | 0 .../reactivecharm/layer.yaml | 0 .../reactivecharm/metadata.yaml | 0 .../reactivecharm/reactive/reactivecharm.py | 0 .../smoketests/reactive-legacy/task.yaml | 37 ++++++++++++ .../reactivecharm/charmcraft.yaml | 15 +++++ .../reactivecharm/config.yaml | 13 +++++ .../reactivecharm/layer.yaml | 1 + .../reactivecharm/metadata.yaml | 12 ++++ .../reactivecharm/reactive/reactivecharm.py | 17 ++++++ .../smoketests/reactive-resolute/task.yaml | 30 ++++++++++ tests/spread/smoketests/reactive/task.yaml | 56 ------------------- 13 files changed, 127 insertions(+), 59 deletions(-) rename tests/spread/smoketests/{reactive => reactive-legacy}/reactivecharm/charmcraft.yaml (74%) rename tests/spread/smoketests/{reactive => reactive-legacy}/reactivecharm/config.yaml (100%) rename tests/spread/smoketests/{reactive => reactive-legacy}/reactivecharm/layer.yaml (100%) rename tests/spread/smoketests/{reactive => reactive-legacy}/reactivecharm/metadata.yaml (100%) rename tests/spread/smoketests/{reactive => reactive-legacy}/reactivecharm/reactive/reactivecharm.py (100%) create mode 100644 tests/spread/smoketests/reactive-legacy/task.yaml create mode 100644 tests/spread/smoketests/reactive-resolute/reactivecharm/charmcraft.yaml create mode 100644 tests/spread/smoketests/reactive-resolute/reactivecharm/config.yaml create mode 100644 tests/spread/smoketests/reactive-resolute/reactivecharm/layer.yaml create mode 100644 tests/spread/smoketests/reactive-resolute/reactivecharm/metadata.yaml create mode 100644 tests/spread/smoketests/reactive-resolute/reactivecharm/reactive/reactivecharm.py create mode 100644 tests/spread/smoketests/reactive-resolute/task.yaml delete mode 100644 tests/spread/smoketests/reactive/task.yaml 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 100% rename from tests/spread/smoketests/reactive/reactivecharm/reactive/reactivecharm.py rename to tests/spread/smoketests/reactive-legacy/reactivecharm/reactive/reactivecharm.py 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 < Date: Tue, 14 Jul 2026 11:40:10 -0400 Subject: [PATCH 32/61] build(deps): update dorny/paths-filter action to v4.0.2 (#2781) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/policy.yaml | 2 +- .github/workflows/qa.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/policy.yaml b/.github/workflows/policy.yaml index bca46a475..c47fe0546 100644 --- a/.github/workflows/policy.yaml +++ b/.github/workflows/policy.yaml @@ -17,7 +17,7 @@ jobs: 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/qa.yaml b/.github/workflows/qa.yaml index 787d2d076..3efd87bf2 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -20,7 +20,7 @@ jobs: 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). From eb2bb9780c4cc6422348758201fd78a2c1ac9966 Mon Sep 17 00:00:00 2001 From: javierdelapuente Date: Tue, 14 Jul 2026 17:45:14 +0200 Subject: [PATCH 33/61] docs: use microk8s 1.34-strict instead of 1.31-strict (#2784) Use microk8s 1.34-strict/stable instead 1.31-strict/stable. Microk8s 1.31 is EOL, and it does not work correctly in Ubuntu 26.04. --- docs/reuse/tutorial/setup_edge.rst | 6 +++--- docs/reuse/tutorial/setup_stable.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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: From f435daa2d213316548218891920d25114765abed Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Wed, 15 Jul 2026 03:55:12 +1200 Subject: [PATCH 34/61] feat: in the profiles, run tests treating warnings as errors (#2718) Promotes Python warnings to errors in `tox -e unit` for both the machine and kubernetes init profiles so charmers catch deprecated-API and resource-cleanup bugs before they bite in production. Validated by an internal Charm Tech baseline (clean after operator#2506/#2507/#2509 land) and a 145-charm downstream baseline (~40% projected pass rate post-operator-fix; residual clusters are all filterable per-repo). Gate PRs merged 2026-06-02; see canonical-work-queue roadmap/26.10/w-error/DECISION.md. * docs: add 4.3 release notes entry for unit-profile warnings-as-errors * docs: Apply suggestion from @tonyandrewmeyer Signed-off-by: Tony Meyer * docs: Update docs/release-notes/charmcraft-4.3.rst Co-authored-by: JJ Coldiron Signed-off-by: Tony Meyer * docs: Update docs/release-notes/charmcraft-4.3.rst Co-authored-by: JJ Coldiron Signed-off-by: Tony Meyer --------- Signed-off-by: Tony Meyer Co-authored-by: JJ Coldiron Co-authored-by: Michael DuBelko --- .../init-kubernetes/pyproject.toml.j2 | 1 + .../templates/init-machine/pyproject.toml.j2 | 1 + docs/release-notes/charmcraft-4.3.rst | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/charmcraft/templates/init-kubernetes/pyproject.toml.j2 b/charmcraft/templates/init-kubernetes/pyproject.toml.j2 index 8bc5a08ae..6ebf764f8 100644 --- a/charmcraft/templates/init-kubernetes/pyproject.toml.j2 +++ b/charmcraft/templates/init-kubernetes/pyproject.toml.j2 @@ -47,6 +47,7 @@ show_missing = true [tool.pytest.ini_options] minversion = "6.0" log_cli_level = "INFO" +filterwarnings = ["error"] # Linting tools configuration [tool.ruff] diff --git a/charmcraft/templates/init-machine/pyproject.toml.j2 b/charmcraft/templates/init-machine/pyproject.toml.j2 index b8f1f8edf..828f34db8 100644 --- a/charmcraft/templates/init-machine/pyproject.toml.j2 +++ b/charmcraft/templates/init-machine/pyproject.toml.j2 @@ -46,6 +46,7 @@ show_missing = true [tool.pytest.ini_options] minversion = "6.0" log_cli_level = "INFO" +filterwarnings = ["error"] # Linting tools configuration [tool.ruff] diff --git a/docs/release-notes/charmcraft-4.3.rst b/docs/release-notes/charmcraft-4.3.rst index 0b49661e9..4b5c904c4 100644 --- a/docs/release-notes/charmcraft-4.3.rst +++ b/docs/release-notes/charmcraft-4.3.rst @@ -88,6 +88,25 @@ test scaffolding: - 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 + + [pytest] + filterwarnings = [ + "error", + "ignore::UserWarning", + ] + COS directory structure validation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From ca4a99500e2917dec9594e206316ac36d50dc64a Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 14 Jul 2026 12:12:50 -0400 Subject: [PATCH 35/61] ci: ignore unfixable OSVs (#2773) Replaces #2765 with the same fix pattern as starbase #573. --------- Signed-off-by: Alex Lowe --- .github/workflows/policy.yaml | 10 +++++++--- osv-scanner.toml | 12 ++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 osv-scanner.toml diff --git a/.github/workflows/policy.yaml b/.github/workflows/policy.yaml index c47fe0546..96b8908e6 100644 --- a/.github/workflows/policy.yaml +++ b/.github/workflows/policy.yaml @@ -40,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/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)" From 389d7dcbc880c6cbd5587c7a5ea9f2c2663cca5d Mon Sep 17 00:00:00 2001 From: Trong Nhan Mai Date: Wed, 15 Jul 2026 02:26:54 +1000 Subject: [PATCH 36/61] docs: update urls and doc string in kubernetes and machine profiles (#2753) This PR fixes #2752 . --- .../init-kubernetes/tests/integration/conftest.py.j2 | 4 ++-- .../init-kubernetes/tests/integration/test_charm.py.j2 | 2 +- .../templates/init-kubernetes/tests/unit/test_charm.py.j2 | 2 +- .../templates/init-machine/tests/integration/conftest.py.j2 | 4 ++-- .../templates/init-machine/tests/integration/test_charm.py.j2 | 2 +- charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) 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..a120ee1d2 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. 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..449362e00 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,7 @@ # 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/ import ops import pytest 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..fd4153b18 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. 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..1bb51e05b 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,7 @@ # 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/ import pytest from ops import testing From e4d619c43b685270d8d7377e52e52446e3cca465 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:31:01 -0400 Subject: [PATCH 37/61] build(deps): lock file maintenance (#2782) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From b29ab2dea78e110a9669679c2fad1b632fa8d107 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 14 Jul 2026 12:51:10 -0400 Subject: [PATCH 38/61] Configure Renovate to ignore docs directory (#2778) Set Renovate ignorePaths to docs/** so dependency updates under docs are not proposed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) 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/**"], } From ff99aac2d426fd6298f5ade591ba86e96037c0a3 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 14 Jul 2026 13:14:06 -0400 Subject: [PATCH 39/61] ci: always discard spread workers in snap-store-tests (#2771) --- .github/workflows/spread.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 923abf5b1..526e74add 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -136,3 +136,11 @@ jobs: CHARM_DEFAULT_NAME: gh-ci-charmcraft-charm run: | spread google: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 From bf48891e86daaaf5a92a7614061764bfe387253b Mon Sep 17 00:00:00 2001 From: Trong Nhan Mai Date: Wed, 15 Jul 2026 09:34:06 +1000 Subject: [PATCH 40/61] update pytest logging options in k8s and machine profiles (#2769) Co-authored-by: Alex Lowe --- charmcraft/templates/init-kubernetes/pyproject.toml.j2 | 2 ++ charmcraft/templates/init-machine/pyproject.toml.j2 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/charmcraft/templates/init-kubernetes/pyproject.toml.j2 b/charmcraft/templates/init-kubernetes/pyproject.toml.j2 index 6ebf764f8..2e73eef64 100644 --- a/charmcraft/templates/init-kubernetes/pyproject.toml.j2 +++ b/charmcraft/templates/init-kubernetes/pyproject.toml.j2 @@ -46,7 +46,9 @@ 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 diff --git a/charmcraft/templates/init-machine/pyproject.toml.j2 b/charmcraft/templates/init-machine/pyproject.toml.j2 index 828f34db8..e116c4dac 100644 --- a/charmcraft/templates/init-machine/pyproject.toml.j2 +++ b/charmcraft/templates/init-machine/pyproject.toml.j2 @@ -45,7 +45,9 @@ 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 From bf3b6478cd4d8e43d9fb8735a07e26fff56c3cd4 Mon Sep 17 00:00:00 2001 From: JJ Coldiron Date: Tue, 14 Jul 2026 21:29:06 -0700 Subject: [PATCH 41/61] docs: fix versioning logic (#2787) --- docs/conf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 250ca0eb1..00d5fc2b2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,8 +35,7 @@ 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) From 9f3567e619452fae550f1ba71c0e514657d0bff5 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 16 Jul 2026 09:57:11 -0400 Subject: [PATCH 42/61] ci: give copilot collections job the right perms (#2796) Replicate canonical/snapcraft#6352. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/copilot-collections-update.yaml | 4 ++++ 1 file changed, 4 insertions(+) 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 From 9279665ac6a34e563a4eb9855fe795f25db3d924 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 17 Jul 2026 10:56:27 -0500 Subject: [PATCH 43/61] feat!: migrate from candid to Ubuntu One auth (#2760) Switches from candid to Ubuntu One for authentication. (CHARMCRAFT-667) --- .github/workflows/spread.yaml | 3 + charmcraft/application/commands/store.py | 46 ++++- charmcraft/const.py | 1 + charmcraft/env.py | 9 +- charmcraft/services/store.py | 186 ++++++++++++------ charmcraft/store/client.py | 9 +- charmcraft/utils/store.py | 20 +- pyproject.toml | 2 +- spread.yaml | 10 +- tests/commands/test_store_api.py | 2 +- tests/commands/test_store_client.py | 24 ++- tests/conftest.py | 6 +- .../store/charm-upload-and-release/task.yaml | 2 +- .../spread/store/name-registration/task.yaml | 18 +- tests/spread/store/ubuntu-sso-login/task.yaml | 15 ++ tests/test_env.py | 12 ++ tests/unit/commands/test_store.py | 78 +++++++- tests/unit/services/test_store.py | 127 +++++++----- tests/unit/utils/test_store.py | 2 +- uv.lock | 10 +- 20 files changed, 405 insertions(+), 177 deletions(-) create mode 100644 tests/spread/store/ubuntu-sso-login/task.yaml diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 526e74add..16d172e7b 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -134,6 +134,9 @@ 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/ diff --git a/charmcraft/application/commands/store.py b/charmcraft/application/commands/store.py index 47ebd1c5d..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 @@ -95,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 @@ -214,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}.") @@ -273,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} diff --git a/charmcraft/const.py b/charmcraft/const.py index 20dd29562..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 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/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/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/pyproject.toml b/pyproject.toml index f6d4586c2..5c89bb877 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dependencies = [ "craft-parts~=2.20", "craft-providers>=3.7.1,<4.0", "craft-platforms~=0.10", - "craft-store~=3.2", + "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..c8dea1392 100644 --- a/spread.yaml +++ b/spread.yaml @@ -147,6 +147,7 @@ prepare: | tests.pkgs install snapd tests.pkgs install unzip + tests.pkgs install expect snap wait system seed.loaded @@ -305,7 +306,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 +329,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/spread/store/charm-upload-and-release/task.yaml b/tests/spread/store/charm-upload-and-release/task.yaml index 9582ca673..b81f25bbe 100644 --- a/tests/spread/store/charm-upload-and-release/task.yaml +++ b/tests/spread/store/charm-upload-and-release/task.yaml @@ -51,7 +51,7 @@ execute: | sleep 10 for i in {1..10} do - sleep 2 + sleep 10 # validate the channel map edge_release=$(charmcraft status $CHARM_DEFAULT_NAME --format=json | jq -r '.[] | select(.track=="latest") | .mappings[0].releases | .[] | select(.channel=="latest/edge")') edge_revision=$(echo $edge_release | jq -r .revision) diff --git a/tests/spread/store/name-registration/task.yaml b/tests/spread/store/name-registration/task.yaml index bd5d880fe..16cf99947 100644 --- a/tests/spread/store/name-registration/task.yaml +++ b/tests/spread/store/name-registration/task.yaml @@ -1,10 +1,9 @@ -summary: register charm and bundle names, verify list, unregister +summary: register a charm name, verify list, unregister systems: - ubuntu-22.04-64 environment: CHARM_NAME: spreadtests-charm-$(uuidgen) - BUNDLE_NAME: spreadtests-bundle-$(uuidgen) include: - tests/ @@ -12,22 +11,7 @@ include: prepare: | tests.pkgs install jq - mkdir charm - cd charm - - cat <<- EOF > 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/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/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_store.py b/tests/unit/commands/test_store.py index 34a37a8d7..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 @@ -53,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, @@ -74,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, @@ -95,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( 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 af548234f..13838a763 100644 --- a/uv.lock +++ b/uv.lock @@ -559,7 +559,7 @@ requires-dist = [ { name = "craft-parts", specifier = "~=2.20" }, { name = "craft-platforms", specifier = "~=0.10" }, { name = "craft-providers", specifier = ">=3.7.1,<4.0" }, - { name = "craft-store", specifier = "~=3.2" }, + { name = "craft-store", specifier = "~=3.4" }, { name = "distro", specifier = ">=1.7.0" }, { name = "docker", specifier = ">=7.0.0" }, { name = "humanize", specifier = ">=2.6.0" }, @@ -1028,7 +1028,7 @@ wheels = [ [[package]] name = "craft-store" -version = "3.3.0" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1043,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]] @@ -1234,7 +1234,7 @@ name = "exceptiongroup" version = "1.3.1" 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 = "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/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ From 86ed7bc20ba68bb4cb9381af583b1f57aee59386 Mon Sep 17 00:00:00 2001 From: JJ Coldiron Date: Fri, 17 Jul 2026 08:57:15 -0700 Subject: [PATCH 44/61] docs: fix typo in plugin migration guide (#2798) Signed-off-by: JJ Coldiron --- docs/howto/migrate-plugins/charm-to-uv.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 3f42ca195b76f568fdc41fdc1ef93e0d711c2701 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Mon, 20 Jul 2026 11:54:32 -0400 Subject: [PATCH 45/61] docs: update pip-compile reference link (#2802) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/howto/migrate-plugins/charm-to-python.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ed45fab594adf86544f61fa296e10fa736bfe8ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:29:32 -0400 Subject: [PATCH 46/61] build(deps): update bugfixes (#2799) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../requirements/requirements-hashes.txt | 125 +++++++++++------- .../charm/requirements.txt | 2 +- .../ubuntu-20.04/charm/bases/requirements.txt | 2 +- .../charm/platforms/requirements.txt | 2 +- .../ubuntu-22.04/charm/bases/requirements.txt | 2 +- .../charm/platforms/requirements.txt | 2 +- .../charm/platforms/requirements.txt | 2 +- .../charm/platforms/requirements.txt | 2 +- .../charm/platforms/requirements.txt | 2 +- 10 files changed, 83 insertions(+), 60 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 466c8deb0..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.20" + rev: "v0.15.22" hooks: # Run the linter - id: ruff 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/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 From 0c23fc55ba62c7fed8d6adc0f322576b32c94189 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:37:41 -0400 Subject: [PATCH 47/61] fix: update Weekly Spread badge to reference spread-manual.yaml (#2786) The README's "Weekly Spread" badge and link pointed to .github/workflows/spread-large.yaml, which no longer exists in the repository. Fixes #2755 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 705bb0ba2f8c811b9b0838ba453a55212395eb47 Mon Sep 17 00:00:00 2001 From: Sambyd <88731119+Semak12345@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:12:19 +0300 Subject: [PATCH 48/61] docs: update Juju LXD reference link (#2797) --------- Signed-off-by: Alex Lowe Co-authored-by: Sam Kosh Co-authored-by: Alex Lowe Co-authored-by: Michael DuBelko --- docs/reference/files/lxd-profile-yaml-file.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 91f9fc06a78dd531ea73eeb5d438b655fecffd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20U=C4=9EUR?= <39213991+alithethird@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:00:39 +0300 Subject: [PATCH 49/61] docs: 12 factor v2 (#2742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: extension dispatch for 12-factor extensions. * chore: cleanup * chore: addback the resolute invalid * chore: revert fmt * chore: create get_project_bases back * chore: cleanup * chore: Bring back old init templates and version the new ones * chore: set 26.04 and 26.10 as experimental for charm plugin * chore: apply comments * chore: use pascalcase for class * chore: doc links * chore: apply comments * fix: experimental * fix: default to v2 and rename v1 frameworks * docs: Update docs for v2 12-factor extensions. * docs: add linting and testing dependencies to project templates * fix(templates): organize imports in new templates * fix(tox): enable tests path in tox.ini templates for various frameworks * fix(docs): standardize tab indentation for Kubernetes log commands in use-web-app-charm.rst * fix(docs): correct indentation and formatting in manage-extensions.rst * fix(docs): update paas-charm library version information and standardize container names in logs * Apply suggestions from code review Co-authored-by: Erin Conley Signed-off-by: Ali UĞUR <39213991+alithethird@users.noreply.github.com> * docs: update extension management guidance for Ubuntu versions * docs: remove outdated extension behavior notes for Ubuntu versions * docs: improve formatting and consistency in manage-extensions guide * retrigger CI --------- Signed-off-by: Ali UĞUR <39213991+alithethird@users.noreply.github.com> Co-authored-by: David Wilding Co-authored-by: Erin Conley Co-authored-by: Michael DuBelko --- .../pyproject.toml.j2 | 19 + .../src/charm.py.j2 | 1 - .../init-django-framework-26.04/tox.ini.j2 | 66 ++- .../init-django-framework/tox.ini.j2 | 2 +- .../pyproject.toml.j2 | 19 + .../src/charm.py.j2 | 1 - .../init-expressjs-framework-26.04/tox.ini.j2 | 66 ++- .../init-expressjs-framework/tox.ini.j2 | 2 +- .../pyproject.toml.j2 | 19 + .../src/charm.py.j2 | 1 - .../init-fastapi-framework-26.04/tox.ini.j2 | 66 ++- .../init-fastapi-framework/tox.ini.j2 | 2 +- .../pyproject.toml.j2 | 19 + .../src/charm.py.j2 | 1 - .../init-flask-framework-26.04/tox.ini.j2 | 66 ++- .../templates/init-flask-framework/tox.ini.j2 | 2 +- .../init-go-framework-26.04/pyproject.toml.j2 | 19 + .../init-go-framework-26.04/src/charm.py.j2 | 1 - .../init-go-framework-26.04/tox.ini.j2 | 66 ++- .../templates/init-go-framework/tox.ini.j2 | 2 +- .../pyproject.toml.j2 | 19 + .../src/charm.py.j2 | 1 - .../tox.ini.j2 | 66 ++- .../init-spring-boot-framework/tox.ini.j2 | 2 +- docs/howto/manage-extensions.rst | 15 +- .../use-web-app-charm.rst | 511 +++++++++++++----- 26 files changed, 690 insertions(+), 364 deletions(-) diff --git a/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 index 85441d1bd..a3c32be91 100644 --- a/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 +++ b/charmcraft/templates/init-django-framework-26.04/pyproject.toml.j2 @@ -25,6 +25,25 @@ 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 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 index 359b47307..4a994d611 100755 --- a/charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 +++ b/charmcraft/templates/init-django-framework-26.04/src/charm.py.j2 @@ -8,7 +8,6 @@ import logging import typing import ops - import paas_charm.django logger = logging.getLogger(__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 index bd15d819c..17bbc92fb 100644 --- a/charmcraft/templates/init-django-framework-26.04/tox.ini.j2 +++ b/charmcraft/templates/init-django-framework-26.04/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] @@ -31,50 +31,46 @@ commands = ruff check --fix {[vars]all_path} [testenv:lint] -description = Check code against coding style standards -deps = - ruff - codespell +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 -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt +runner = uv-venv-lock-runner +dependency_groups = + unit commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} coverage report -[testenv:static] -description = Run static type checks -deps = - pyright - -r {tox_root}/requirements.txt -commands = - pyright {posargs} - [testenv:integration] description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt +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 \ - {posargs} \ - {[vars]tests_path}/integration + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} 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/pyproject.toml.j2 b/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 index 85441d1bd..a3c32be91 100644 --- a/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 +++ b/charmcraft/templates/init-expressjs-framework-26.04/pyproject.toml.j2 @@ -25,6 +25,25 @@ 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 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 index 798b68376..c64fe9ee8 100755 --- a/charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 +++ b/charmcraft/templates/init-expressjs-framework-26.04/src/charm.py.j2 @@ -8,7 +8,6 @@ import logging import typing import ops - import paas_charm.expressjs logger = logging.getLogger(__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 index bd15d819c..17bbc92fb 100644 --- a/charmcraft/templates/init-expressjs-framework-26.04/tox.ini.j2 +++ b/charmcraft/templates/init-expressjs-framework-26.04/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] @@ -31,50 +31,46 @@ commands = ruff check --fix {[vars]all_path} [testenv:lint] -description = Check code against coding style standards -deps = - ruff - codespell +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 -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt +runner = uv-venv-lock-runner +dependency_groups = + unit commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} coverage report -[testenv:static] -description = Run static type checks -deps = - pyright - -r {tox_root}/requirements.txt -commands = - pyright {posargs} - [testenv:integration] description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt +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 \ - {posargs} \ - {[vars]tests_path}/integration + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} 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/pyproject.toml.j2 b/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 index 85441d1bd..a3c32be91 100644 --- a/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 +++ b/charmcraft/templates/init-fastapi-framework-26.04/pyproject.toml.j2 @@ -25,6 +25,25 @@ 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 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 index 84f9fa77a..59042cc4a 100755 --- a/charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 +++ b/charmcraft/templates/init-fastapi-framework-26.04/src/charm.py.j2 @@ -8,7 +8,6 @@ import logging import typing import ops - import paas_charm.fastapi logger = logging.getLogger(__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 index bd15d819c..17bbc92fb 100644 --- a/charmcraft/templates/init-fastapi-framework-26.04/tox.ini.j2 +++ b/charmcraft/templates/init-fastapi-framework-26.04/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] @@ -31,50 +31,46 @@ commands = ruff check --fix {[vars]all_path} [testenv:lint] -description = Check code against coding style standards -deps = - ruff - codespell +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 -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt +runner = uv-venv-lock-runner +dependency_groups = + unit commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} coverage report -[testenv:static] -description = Run static type checks -deps = - pyright - -r {tox_root}/requirements.txt -commands = - pyright {posargs} - [testenv:integration] description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt +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 \ - {posargs} \ - {[vars]tests_path}/integration + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} 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/pyproject.toml.j2 b/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 index 85441d1bd..a3c32be91 100644 --- a/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 +++ b/charmcraft/templates/init-flask-framework-26.04/pyproject.toml.j2 @@ -25,6 +25,25 @@ 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 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 index 94cb3f33f..76f7e7d62 100755 --- a/charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 +++ b/charmcraft/templates/init-flask-framework-26.04/src/charm.py.j2 @@ -8,7 +8,6 @@ import logging import typing import ops - import paas_charm.flask logger = logging.getLogger(__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 index bd15d819c..17bbc92fb 100644 --- a/charmcraft/templates/init-flask-framework-26.04/tox.ini.j2 +++ b/charmcraft/templates/init-flask-framework-26.04/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] @@ -31,50 +31,46 @@ commands = ruff check --fix {[vars]all_path} [testenv:lint] -description = Check code against coding style standards -deps = - ruff - codespell +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 -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt +runner = uv-venv-lock-runner +dependency_groups = + unit commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} coverage report -[testenv:static] -description = Run static type checks -deps = - pyright - -r {tox_root}/requirements.txt -commands = - pyright {posargs} - [testenv:integration] description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt +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 \ - {posargs} \ - {[vars]tests_path}/integration + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} 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/pyproject.toml.j2 b/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 index 85441d1bd..a3c32be91 100644 --- a/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 +++ b/charmcraft/templates/init-go-framework-26.04/pyproject.toml.j2 @@ -25,6 +25,25 @@ 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 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 index c32223b7e..969734515 100755 --- a/charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 +++ b/charmcraft/templates/init-go-framework-26.04/src/charm.py.j2 @@ -8,7 +8,6 @@ import logging import typing import ops - import paas_charm.go logger = logging.getLogger(__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 index bd15d819c..17bbc92fb 100644 --- a/charmcraft/templates/init-go-framework-26.04/tox.ini.j2 +++ b/charmcraft/templates/init-go-framework-26.04/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] @@ -31,50 +31,46 @@ commands = ruff check --fix {[vars]all_path} [testenv:lint] -description = Check code against coding style standards -deps = - ruff - codespell +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 -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt +runner = uv-venv-lock-runner +dependency_groups = + unit commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} coverage report -[testenv:static] -description = Run static type checks -deps = - pyright - -r {tox_root}/requirements.txt -commands = - pyright {posargs} - [testenv:integration] description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt +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 \ - {posargs} \ - {[vars]tests_path}/integration + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} 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-spring-boot-framework-26.04/pyproject.toml.j2 b/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 index 46991a3ea..96b231689 100644 --- a/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 +++ b/charmcraft/templates/init-spring-boot-framework-26.04/pyproject.toml.j2 @@ -25,6 +25,25 @@ 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 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 index cb3178463..a90bbe2de 100755 --- 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 @@ -8,7 +8,6 @@ import logging import typing import ops - import paas_charm.springboot logger = logging.getLogger(__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 index bd15d819c..17bbc92fb 100644 --- a/charmcraft/templates/init-spring-boot-framework-26.04/tox.ini.j2 +++ b/charmcraft/templates/init-spring-boot-framework-26.04/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] @@ -31,50 +31,46 @@ commands = ruff check --fix {[vars]all_path} [testenv:lint] -description = Check code against coding style standards -deps = - ruff - codespell +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 -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt +runner = uv-venv-lock-runner +dependency_groups = + unit commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit + coverage run --source={[vars]src_path} -m pytest \ + -v \ + -s \ + --tb native \ + {[vars]tests_path}/unit \ + {posargs} coverage report -[testenv:static] -description = Run static type checks -deps = - pyright - -r {tox_root}/requirements.txt -commands = - pyright {posargs} - [testenv:integration] description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt +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 \ - {posargs} \ - {[vars]tests_path}/integration + pytest \ + -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {[vars]tests_path}/integration \ + {posargs} 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/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/use-web-app-charm.rst b/docs/howto/manage-web-app-charms/use-web-app-charm.rst index 35cd431c1..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 + + .. 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 - juju ssh /0 \ - PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/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 + + .. tab-item:: Go + :sync: go - juju ssh --container app /0 pebble plan + .. code-block:: bash - .. tab-item:: Spring Boot - :sync: spring-boot + juju ssh --container app /0 pebble plan - .. code-block:: bash + .. tab-item:: Spring Boot + :sync: spring-boot + + .. code-block:: bash + + 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:: - juju ssh /0 \ - PEBBLE_SOCKET=/charm/containers/app/pebble.socket /charm/bin/pebble plan + .. 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 + + .. 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 + juju ssh /0 \ + PEBBLE_SOCKET=/charm/containers/app/pebble.socket \ + /charm/bin/pebble exec --context=spring-boot -- bash .. important:: @@ -293,119 +474,171 @@ 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 .. tab-set:: - .. tab-item:: MicroK8s - :sync: microk8s + .. tab-item:: Django + :sync: django .. code-block:: bash microk8s kubectl logs -n -c django-app - .. tab-item:: Canonical K8s - :sync: canonical-k8s + .. tab-item:: Express + :sync: express .. code-block:: bash - sudo k8s kubectl logs -n -c django-app + microk8s kubectl logs -n -c app - .. tab-item:: Express - :sync: express + .. tab-item:: FastAPI + :sync: fastapi - .. tab-set:: + .. code-block:: bash - .. tab-item:: MicroK8s - :sync: microk8s + microk8s kubectl logs -n -c app + + .. tab-item:: Flask + :sync: flask + + .. code-block:: bash + + microk8s kubectl logs -n -c flask-app + + .. tab-item:: Go + :sync: go .. code-block:: bash microk8s kubectl logs -n -c app - .. tab-item:: Canonical K8s - :sync: canonical-k8s + .. tab-item:: Spring Boot + :sync: spring-boot .. code-block:: bash - sudo k8s kubectl logs -n -c app + microk8s kubectl logs -n -c app - .. tab-item:: FastAPI - :sync: fastapi + .. tab-item:: Ubuntu 26.04 and higher + :sync: base-26-plus .. tab-set:: - .. tab-item:: MicroK8s - :sync: microk8s + .. tab-item:: Django + :sync: django - .. code-block:: bash + .. tab-set:: - microk8s kubectl logs -n -c app + .. tab-item:: MicroK8s + :sync: microk8s - .. tab-item:: Canonical K8s - :sync: canonical-k8s + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c app - sudo k8s kubectl logs -n -c app + .. tab-item:: Canonical K8s + :sync: canonical-k8s - .. tab-item:: Flask - :sync: flask + .. code-block:: bash - .. tab-set:: + sudo k8s kubectl logs -n -c app - .. tab-item:: MicroK8s - :sync: microk8s + .. tab-item:: Express + :sync: express - .. code-block:: bash + .. tab-set:: - microk8s kubectl logs -n -c flask-app + .. tab-item:: MicroK8s + :sync: microk8s - .. tab-item:: Canonical K8s - :sync: canonical-k8s + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c app - sudo k8s kubectl logs -n -c flask-app + .. tab-item:: Canonical K8s + :sync: canonical-k8s - .. tab-item:: Go - :sync: go + .. code-block:: bash - .. tab-set:: + sudo k8s kubectl logs -n -c app - .. tab-item:: MicroK8s - :sync: microk8s + .. tab-item:: FastAPI + :sync: fastapi - .. code-block:: bash + .. tab-set:: - microk8s kubectl logs -n -c app + .. tab-item:: MicroK8s + :sync: microk8s - .. tab-item:: Canonical K8s - :sync: canonical-k8s + .. code-block:: bash - .. code-block:: bash + microk8s kubectl logs -n -c app - sudo k8s kubectl logs -n -c app + .. tab-item:: Canonical K8s + :sync: canonical-k8s - .. tab-item:: Spring Boot - :sync: spring-boot + .. code-block:: bash - .. tab-set:: + sudo k8s kubectl logs -n -c app - .. tab-item:: MicroK8s - :sync: microk8s + .. tab-item:: Flask + :sync: flask - .. code-block:: bash + .. tab-set:: - microk8s kubectl logs -n -c app + .. tab-item:: MicroK8s + :sync: microk8s - .. tab-item:: Canonical K8s - :sync: canonical-k8s + .. code-block:: bash - .. 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 - sudo k8s kubectl logs -n -c app + sudo k8s kubectl logs -n -c app .. seealso:: From e7bfc4380a78961d82e2772ecb6cbe3df515fd78 Mon Sep 17 00:00:00 2001 From: smethnani <82812166+smethnani@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:52:52 +0100 Subject: [PATCH 50/61] ci: use openstack for spread (#2783) Switches spread backend from google to openstack --- .github/workflows/spread.yaml | 12 +-- docs/release-notes/charmcraft-4.3.rst | 2 +- spread.yaml | 88 +++++++++++++++++-- .../reactivecharm/reactive/reactivecharm.py | 2 +- tests/spread/store/resources/task.yaml | 8 +- tests/spread/tools.sh | 17 ++++ 6 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 tests/spread/tools.sh diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 16d172e7b..d9b2ae518 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -62,7 +62,7 @@ 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: @@ -71,10 +71,10 @@ jobs: - 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 @@ -100,7 +100,7 @@ jobs: - name: Run spread run: | - spread google:${{ matrix.system }} + spread openstack:${{ matrix.system }} - name: Discard spread workers if: always() @@ -111,7 +111,7 @@ jobs: done snap-store-tests: - runs-on: spread-installed + runs-on: [self-hosted, spread-enabled] needs: [snap-build] steps: @@ -138,7 +138,7 @@ jobs: 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() diff --git a/docs/release-notes/charmcraft-4.3.rst b/docs/release-notes/charmcraft-4.3.rst index 4b5c904c4..4a33dda96 100644 --- a/docs/release-notes/charmcraft-4.3.rst +++ b/docs/release-notes/charmcraft-4.3.rst @@ -101,7 +101,7 @@ To ignore a warning across the project, add an ``ignore`` entry to your .. code-block:: toml :caption: pyproject.toml - [pytest] + [tool.pytest.ini_options] filterwarnings = [ "error", "ignore::UserWarning", diff --git a/spread.yaml b/spread.yaml index c8dea1392..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." @@ -158,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 @@ -170,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" @@ -265,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 @@ -274,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 @@ -298,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 diff --git a/tests/spread/smoketests/reactive-legacy/reactivecharm/reactive/reactivecharm.py b/tests/spread/smoketests/reactive-legacy/reactivecharm/reactive/reactivecharm.py index f26647159..2ce29952b 100644 --- a/tests/spread/smoketests/reactive-legacy/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/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/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 +} From c1720ba17dc33895a3b7727a022b2308f070c29c Mon Sep 17 00:00:00 2001 From: minulo Date: Tue, 21 Jul 2026 14:58:20 -0400 Subject: [PATCH 51/61] docs: fix caption paths in the Django tutorial (#2805) The captions for settings.py and the project urls.py were missing the inner django_hello_world package directory that django-admin startproject creates. Correct them to match the actual layout used by the tutorial's task.yaml spread test. --- docs/tutorial/kubernetes-charm-django.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/kubernetes-charm-django.rst b/docs/tutorial/kubernetes-charm-django.rst index adba3d59b..f0aae12c9 100644 --- a/docs/tutorial/kubernetes-charm-django.rst +++ b/docs/tutorial/kubernetes-charm-django.rst @@ -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 From c79e747e184ec6f4bd9267d646d43d0dbe62c07e Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 21 Jul 2026 15:14:03 -0400 Subject: [PATCH 52/61] build(deps): update craft dependencies for 4.4.0 (#2803) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyproject.toml | 8 ++++---- uv.lock | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5c89bb877..1cce982c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,12 @@ 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-grammar>=2.3.0", + "craft-parts~=2.35", "craft-providers>=3.7.1,<4.0", - "craft-platforms~=0.10", + "craft-platforms~=0.11", "craft-store~=3.4", "distro>=1.7.0", "docker>=7.0.0", diff --git a/uv.lock b/uv.lock index 13838a763..ed7cbdc72 100644 --- a/uv.lock +++ b/uv.lock @@ -553,11 +553,11 @@ 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-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" }, From 82e9de8b78a07949d53efa05d4c89c27bbfd8847 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 21 Jul 2026 15:31:27 -0400 Subject: [PATCH 53/61] docs: add Charmcraft 4.4 release notes (#2801) Add Charmcraft 4.4 release notes updates. Release notes preview: https://canonical-charmcraft--2801.com.readthedocs.build/2801/release-notes/charmcraft-4.4/ CHARMCRAFT-711 --- docs/conf.py | 1 - docs/release-notes/charmcraft-4.4.rst | 65 +++++++++++++- docs/release-notes/charmcraft-4.5.rst | 124 ++++++++++++++++++++++++++ docs/release-notes/index.rst | 2 + 4 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 docs/release-notes/charmcraft-4.5.rst diff --git a/docs/conf.py b/docs/conf.py index 00d5fc2b2..87e19df2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -194,7 +194,6 @@ # Excludes files or directories from processing exclude_patterns = [ - "release-notes/charmcraft-4.4.rst", "README.md", # Docs README "reuse", "common/craft-parts/explanation/lifecycle.rst", diff --git a/docs/release-notes/charmcraft-4.4.rst b/docs/release-notes/charmcraft-4.4.rst index 318a8e37f..08d872c1e 100644 --- a/docs/release-notes/charmcraft-4.4.rst +++ b/docs/release-notes/charmcraft-4.4.rst @@ -4,7 +4,7 @@ .. _release-4.4: Charmcraft 4.4 release notes -============================= +============================ 28 July 2026 @@ -38,11 +38,42 @@ 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: +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 @@ -53,9 +84,37 @@ 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. +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 107f34e48..c2be23597 100644 --- a/docs/release-notes/index.rst +++ b/docs/release-notes/index.rst @@ -14,6 +14,7 @@ Current releases Charmcraft 4 ~~~~~~~~~~~~ +- :doc:`Charmcraft 4.4 ` - :doc:`Charmcraft 4.3 ` - :doc:`Charmcraft 4.2 ` - :doc:`Charmcraft 4.1 ` @@ -89,6 +90,7 @@ 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 From a2503a34fad32de497b95c19ae355121a54327a8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:41:00 -0400 Subject: [PATCH 54/61] chore: update Copilot collections to v0.15.0 (#2807) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.copilot-collections.yaml | 2 +- .../instructions/cloud-agent.instructions.md | 73 +++++++++++++++++++ .../starcraft-docs-meta.instructions.md | 60 +++++---------- 3 files changed, 94 insertions(+), 41 deletions(-) create mode 100644 .github/instructions/cloud-agent.instructions.md 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. From dad40b2e8bf06f96b3e90cbc456f7e1fe0dc104e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:09:22 +0000 Subject: [PATCH 55/61] =?UTF-8?q?feat(profile):=20k8s=20=E2=80=94=20wire?= =?UTF-8?q?=20Prometheus=20metrics,=20Loki=20logs,=20Grafana=20dashboards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add MetricsEndpointProvider, LogForwarder, and GrafanaDashboardProvider to the kubernetes profile scaffold so that `charmcraft init --profile=kubernetes` produces a charm pre-wired for Canonical Observability Stack integration. - charmcraft.yaml: declare charm-libs (prometheus_scrape v0, loki_push v1, grafana_dashboard v0), provides: metrics-endpoint + grafana-dashboard, requires: logging - src/charm.py: import and instantiate all three providers in __init__ with comments pointing developers to the scrape port and fetch-libs step --- .../init-kubernetes/charmcraft.yaml.j2 | 30 +++++++++++++++++++ .../templates/init-kubernetes/src/charm.py.j2 | 17 +++++++++++ 2 files changed, 47 insertions(+) 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/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") From 202c7350b64d760c601c4a83f2b2405f37410e27 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:09:28 +0000 Subject: [PATCH 56/61] =?UTF-8?q?feat(profile):=20k8s=20=E2=80=94=20add=20?= =?UTF-8?q?starter=20Grafana=20dashboard=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add src/grafana_dashboards/overview.json.j2 to the kubernetes profile. GrafanaDashboardProvider discovers and ships JSON files from this directory automatically via the grafana-dashboard relation. The starter dashboard includes an 'up' stat panel (Prometheus scrape health) and a markdown panel with getting-started guidance. --- .../src/grafana_dashboards/overview.json.j2 | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 charmcraft/templates/init-kubernetes/src/grafana_dashboards/overview.json.j2 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} + } + ] +} From f85a19c74a7ab4090a15375b45eaa7500e7ba343 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:09:34 +0000 Subject: [PATCH 57/61] =?UTF-8?q?feat(profile):=20k8s=20=E2=80=94=20observ?= =?UTF-8?q?ability=20unit=20and=20integration=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unit tests (ops.testing.Context): - test_metrics_endpoint_relation_joined: verifies MetricsEndpointProvider writes scrape_jobs (port 8080) into app relation data on relation-joined - test_grafana_dashboard_relation_joined: verifies GrafanaDashboardProvider writes dashboard data into app relation data - test_logging_relation_joined: verifies LogForwarder handles relation-joined without error on the requirer side Integration tests (jubilant, @pytest.mark.skip): - Skipped stubs for metrics collection, dashboard registration, and log forwarding that show the jubilant + COS integration pattern; include instructions for enabling against a real COS lite stack. --- .../tests/integration/test_charm.py.j2 | 79 +++++++++++++++++++ .../tests/unit/test_charm.py.j2 | 68 ++++++++++++++++ 2 files changed, 147 insertions(+) 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 a120ee1d2..53ea8cb95 100644 --- a/charmcraft/templates/init-kubernetes/tests/integration/test_charm.py.j2 +++ b/charmcraft/templates/init-kubernetes/tests/integration/test_charm.py.j2 @@ -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 449362e00..963d8cc96 100644 --- a/charmcraft/templates/init-kubernetes/tests/unit/test_charm.py.j2 +++ b/charmcraft/templates/init-kubernetes/tests/unit/test_charm.py.j2 @@ -2,6 +2,11 @@ # See LICENSE file for licensing details. # # 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) From 4554b5bc0b7a3c383085ad0894d02513b74f33d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:10:49 +0000 Subject: [PATCH 58/61] =?UTF-8?q?feat(profile):=20machine=20=E2=80=94=20wi?= =?UTF-8?q?re=20COS=20observability=20via=20COSAgentProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add COSAgentProvider to the machine profile scaffold so that `charmcraft init --profile=machine` produces a charm pre-wired for Canonical Observability Stack integration via the grafana-agent machine charm. COSAgentProvider bundles metrics scraping, log forwarding, and dashboard distribution through a single 'cos-agent' relation to grafana-agent. - charmcraft.yaml: declare cos_agent charm-lib v0, provides: cos-agent - src/charm.py: import and instantiate COSAgentProvider with a configured metrics endpoint (port 8080) and dashboard directory --- .../templates/init-machine/charmcraft.yaml.j2 | 17 +++++++++++++++++ .../templates/init-machine/src/charm.py.j2 | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) 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/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() From fabaed9dc4a57ad929707f0bd524ba8cbbd64785 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:10:56 +0000 Subject: [PATCH 59/61] =?UTF-8?q?feat(profile):=20machine=20=E2=80=94=20ad?= =?UTF-8?q?d=20starter=20Grafana=20dashboard=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add src/grafana_dashboards/overview.json.j2 to the machine profile. COSAgentProvider reads JSON files from this directory and ships them to Grafana via grafana-agent and the COS stack. Mirrors the kubernetes profile dashboard structure and guidance. --- .../src/grafana_dashboards/overview.json.j2 | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 charmcraft/templates/init-machine/src/grafana_dashboards/overview.json.j2 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} + } + ] +} From 753ebfe7be5a949c2abf104e1cb3056de5392293 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:11:02 +0000 Subject: [PATCH 60/61] =?UTF-8?q?feat(profile):=20machine=20=E2=80=94=20ob?= =?UTF-8?q?servability=20unit=20and=20integration=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unit tests (ops.testing.Context): - test_cos_agent_relation_joined: verifies COSAgentProvider writes 'config' into unit relation data, and that metrics_scrape_jobs contains the port-8080 endpoint declared in charm.__init__ - test_cos_agent_relation_joined_dashboard_registered: verifies the dashboard directory is included in the COS agent config Integration tests (jubilant, @pytest.mark.skip): - Skipped stubs for cos-agent relation and metrics scraping; include comments explaining how to enable against a real grafana-agent + COS stack. --- .../tests/integration/test_charm.py.j2 | 57 +++++++++++++++++++ .../init-machine/tests/unit/test_charm.py.j2 | 51 +++++++++++++++++ 2 files changed, 108 insertions(+) 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 fd4153b18..765d819f9 100644 --- a/charmcraft/templates/init-machine/tests/integration/test_charm.py.j2 +++ b/charmcraft/templates/init-machine/tests/integration/test_charm.py.j2 @@ -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 1bb51e05b..db211a57d 100644 --- a/charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 +++ b/charmcraft/templates/init-machine/tests/unit/test_charm.py.j2 @@ -2,6 +2,11 @@ # See LICENSE file for licensing details. # # 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" From abeb07a84794609b718ff085a2f0935cc6d43efe Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Mon, 27 Jul 2026 19:19:16 +1200 Subject: [PATCH 61/61] test(init): expect the observability dashboard files in init output The observability profile adds src/grafana_dashboards/overview.json to both the kubernetes and machine scaffolds, so BASIC_INIT_FILES has to list it. --- tests/integration/commands/test_init.py | 2 ++ 1 file changed, 2 insertions(+) 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",