diff --git a/.github/workflows/code-test.yaml b/.github/workflows/code-test.yaml index c1b35e18e..bc68b5814 100644 --- a/.github/workflows/code-test.yaml +++ b/.github/workflows/code-test.yaml @@ -58,13 +58,35 @@ jobs: if [ -e .stestr.conf ]; then uv run stestr run else - uv run pytest --cov --cov-report xml:coverage.xml + uv run pytest --cov --cov-report xml:coverage.xml --junitxml=junit.xml fi - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: coverage-${{ matrix.project }} path: python/${{ matrix.project }}/coverage.xml retention-days: 1 + # Publish the ML2 scenario traceability matrix (scenario ID -> status) to + # the job summary and keep the JUnit as an artifact. neutron-understack + # only: its scenario tests tag each JUnit case with a `scenario` property. + - name: ML2 scenario traceability report + if: ${{ always() && matrix.project == 'neutron-understack' }} + run: | + if [ -f junit.xml ]; then + uv run python ../../scripts/scenario-report.py \ + --catalog neutron_understack/tests/scenarios/SCENARIOS.md \ + --junit junit.xml >> "$GITHUB_STEP_SUMMARY" + else + echo "No junit.xml produced (tests did not run)." >> "$GITHUB_STEP_SUMMARY" + fi + # Keep each project's JUnit for the consolidated publish-test-results job. + # if-no-files-found=ignore: stestr projects don't emit junit.xml. + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ always() }} + with: + name: junit-${{ matrix.project }} + path: python/${{ matrix.project }}/junit.xml + if-no-files-found: ignore + retention-days: 1 coverage-upload: @@ -82,3 +104,27 @@ jobs: name: coverage.xml path: coverage.xml retention-days: 1 + + # Consolidated pass/fail/skip report from every project's JUnit, posted as a + # PR comment + check. Same-repo PRs only get write permissions; fork PRs would + # need a separate workflow_run-triggered workflow (not wired up here). + publish-test-results: + needs: [uv] + # Skip on fork PRs: the token is read-only there, so the action cannot post + # the check/comment. Forks would need a separate workflow_run workflow. + if: ${{ always() && github.event.pull_request.head.repo.fork != true }} + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + contents: read + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + pattern: junit-* + merge-multiple: true + path: junit + - uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2.24.0 + with: + check_name: Python test results + junit_files: junit/**/*.xml diff --git a/containers/neutron/Dockerfile b/containers/neutron/Dockerfile index 9afa4a76e..0ef7b9c27 100644 --- a/containers/neutron/Dockerfile +++ b/containers/neutron/Dockerfile @@ -21,9 +21,14 @@ ARG OPENSTACK_VERSION="required_argument" ADD https://releases.openstack.org/constraints/upper/${OPENSTACK_VERSION} /upper-constraints.txt RUN sed -i '/^neutron-lib==.*/d' /upper-constraints.txt +# --no-sources ignores neutron-understack's [tool.uv.sources] (a dev/test-only +# pin of neutron/neutron-lib to the understack/2026.1 git forks). Here we install +# those forks explicitly from /src, so honoring the git sources would just create +# a conflicting-URL error for neutron-lib. RUN --mount=type=cache,target=/root/.cache/uv \ uv pip install \ --upgrade \ + --no-sources \ --constraint /upper-constraints.txt \ /src/neutron \ /src/neutron-lib \ diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/SCENARIOS.md b/python/neutron-understack/neutron_understack/tests/scenarios/SCENARIOS.md new file mode 100644 index 000000000..efff39b52 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/SCENARIOS.md @@ -0,0 +1,322 @@ +# ML2 mechanism scenario catalog + +This is the human-readable **test plan** for the `neutron-understack` ML2 mechanism +drivers. Each scenario below has a stable ID and describes the operation it models +and the **mechanism calls + data** we expect to observe. + +Every scenario here must be implemented by at least one test tagged +`@pytest.mark.scenario("")`, and every such marker must reference a scenario +that exists here. `conftest.py` validates collected markers and enforces this +equivalence whenever the complete scenario package is collected. So this file +and the tests cannot silently drift apart in the scenario CI run. + +Scenario IDs are declared as `### ` headings. Ideas under +"Planned / future" are intentionally *not* IDs (no `###` heading) so they are not +treated as catalogued-but-untested. + +## Baremetal port binding (Ironic vif-attach) + +Runtime chain: `mechanism_drivers = ovn,understack,baremetal,undersync`. These +scenarios load `logger,understack,undersync` (OVN is only needed for router +scenarios). Tenant networks are VXLAN; the physnet named in the binding profile +(`physnet1` in tests) is a VLAN group. + +### BM-BIND-01 — baremetal vif-attach binds hierarchically +- given: a VXLAN tenant network and an unbound baremetal port +- when: the port is updated with `binding:host_id` + a binding profile carrying + `physical_network=<physnet>` and `local_link_information` (the Ironic vif-attach) +- then: + - `understack` binds the VXLAN segment and hands down a dynamically allocated + VLAN segment on `<physnet>` (`continue_binding`) + - `undersync` binds that VLAN segment (`set_binding`, `vif_type=OTHER`, + `status=ACTIVE`) + - two binding levels result: level 0 driver `understack` (VXLAN), + level 1 driver `undersync` (dynamic VLAN) + - `undersync.sync(<physnet>)` reconciles the switch + +### BM-BIND-02 — vif-attach without physical_network refuses binding +- given: a VXLAN tenant network and an unbound baremetal port +- when: vif-attach supplies a binding profile with **no** `physical_network` +- then: `understack` refuses to bind (no `continue_binding`), the port ends + `binding:vif_type=binding_failed` with no binding levels, and + `undersync.sync` is not called + +### BM-BIND-03 — unsupported vnic_type is not bound by us +- given: a VXLAN tenant network and a port with an unsupported vnic_type + (e.g. `direct`) +- when: vif-attach is attempted +- then: neither `understack` nor `undersync` binds it (`binding_failed`), and + `undersync.sync` is not called + +### BM-BIND-04 — vif-detach unbinds, reconciles, and releases the VLAN segment +- given: a bound baremetal port (per BM-BIND-01) +- when: the binding is cleared (`binding:host_id=""`, empty profile) +- then: the port returns to `binding:vif_type=unbound`, + `undersync.sync(<physnet>)` reconciles the switch, and the dynamic VLAN segment + is **released** so its VLAN id returns to the pool +- status: **KNOWN BUG (xfail)** — the driver does not release the segment on + detach today (see "Known bugs"). The test asserts the desired behavior and is + marked `xfail(strict=True)`, so it will start failing (prompting removal of the + xfail) once the bug is fixed. + +### BM-BIND-05 — port delete releases the dynamic VLAN segment +- given: a bound baremetal port (per BM-BIND-01) +- when: the port is deleted +- then: the dynamic VLAN segment is released (no ports remain bound to it) and + `undersync.sync(<physnet>)` reconciles the switch + +### BM-BIND-06 — vif-attach with no IP still emits the physnet sync +- given: a VXLAN network with a subnet, and a baremetal port created with no + fixed IP (`fixed_ips: []`) +- when: the port is vif-attached (host + profile with `physical_network`) +- then: the port binds and `undersync.sync(<physnet>)` still fires — the sync + does not depend on the port having an IP + +### BM-BIND-REUSE-01 — second port on same network+physnet reuses the VLAN seg +- given: a VXLAN network with one bound baremetal port on `physnet1` +- when: a second baremetal port on the same network is vif-attached to `physnet1` +- then: it reuses the existing dynamic VLAN segment (same segment id), no new + allocation + +### PROV-BIND-01 — provisioning-network port binds +- given: a network configured as `ml2_understack.provisioning_network` +- when: a baremetal port on it is vif-attached +- then: it binds hierarchically and `undersync.sync(<physnet>)` fires (no special + casing on the bind path) + +### PROV-DEL-01 — provisioning-network delete retains the VLAN segment +- given: a bound baremetal port on the provisioning network +- when: the port is deleted +- then: `undersync.sync(<physnet>)` fires but the dynamic VLAN segment is + retained (the clean/provision cycle reuses it), unlike a tenant port + (BM-BIND-05) + +## Trunk subport operations + +These scenarios load the real neutron trunk service plugin +(`UnderstackMl2TrunkScenarioBase`). The parent is a bound baremetal port; subport +adds/removes must reconcile the parent's switch (VLAN group). +`utils.fetch_network_node_trunk_id` (live OVN + Ironic discovery) is stubbed. + +### TRUNK-SUB-ADD — subport attach syncs the parent's physnet +- given: a bound baremetal parent port on `physnet1` with a trunk, and a subport + port on another network +- when: the subport is added to the trunk (VLAN segmentation) +- then: the trunk records the subport, a level-0 `understack` binding points it + at a dynamic VLAN segment on `physnet1`, and `undersync.sync(physnet1)` + reconciles the parent port's switch + +### TRUNK-SUB-DEL — subport removal syncs the parent's physnet +- given: the TRUNK-SUB-ADD setup with the subport attached +- when: the subport is removed from the trunk +- then: the trunk result no longer contains the subport, its synthetic binding + level is deleted, its now-unused dynamic VLAN segment is released, and + `undersync.sync(physnet1)` reconciles the parent port's switch + +### TRUNK-PARENT-NOIP — subport add syncs when the parent has no IP +- given: a bound baremetal parent on a subnetted network but with no fixed IP, + plus a trunk and a subport on another network +- when: the subport is added +- then: the subport binding and dynamic segment are created and + `undersync.sync(physnet1)` still fires — a parent with no IP does not suppress + the reconcile + +### TRUNK-DEL-01 — trunk delete syncs the parent's physnet +- given: a bound baremetal parent with a trunk and an attached subport +- when: the trunk is deleted +- then: the trunk is gone, the subport binding and now-unused dynamic segment + are deleted, the parent switchport is cleaned, and `undersync.sync(physnet1)` + fires + +### TRUNK-MULTI-01 — adding multiple subports syncs the parent's physnet +- given: a bound baremetal parent with a trunk +- when: two subports on different networks are added in one operation +- then: each subport has its own level-0 binding to a dynamic VLAN segment on + `physnet1`, the segments are distinct, and `undersync.sync(physnet1)` fires + +### TRUNK-PARENT-UNBOUND-01 — subport add with an unbound parent is a no-op +- given: an unbound (plain) parent port with a trunk +- when: a subport is added +- then: trunk membership is recorded, but no subport binding level or dynamic + VLAN segment is created and no `undersync.sync` occurs + +### TRUNK-SEGID-RANGE-01 — subport seg_id outside the allowed range is rejected +- given: a bound baremetal parent with a trunk +- when: a subport is added with a segmentation_id outside `[1, 3799]` +- then: `SubportSegmentationIDError` (raised in the SUBPORTS PRECOMMIT_CREATE + callback, surfaced as `CallbackFailure`), with no binding level or dynamic + segment allocated + +## Router interface (VRF & SVI flavors) + +These scenarios load a real L3 router + flavors plugin (`ML2TestFramework`). The +VRF and SVI flavored router paths skip the OVN uplink work, so no OVN IDL fake is +needed. The flavored path is simulated by patching `routers._router_has_flavor` +(and `svi._is_svi_router` for the SVI scenarios), mirroring the existing unit +tests. + +The VRF and SVI flavors are close cousins: both realize the router as an SVI +(switched virtual interface) on the switching fabric. The difference is only +which VRF the SVI lands in — the VRF flavor first creates a dedicated VRF and +places the SVI into it, while the SVI flavor drops the SVI into a well-known VRF +that is part of the base switch configuration. So their scenarios are near +mirror images of each other. + +### VRF-ROUTER-ATTACH-01 — VRF router attach syncs bound baremetal port physnets +- given: a VXLAN network + subnet with baremetal ports bound to two different + physnets (`physnet1`, `physnet2`) +- when: a VRF router is created and the subnet is attached on the internal side +- then: undersync syncs each physnet carrying the network's baremetal ports so + the switches are reconciled for the new router +- status: **KNOWN BUG (xfail, rackerlabs/understack#2240)** — attaching the VRF + router interface does not sync those physnets today (`undersync.sync` is never + called for them). The test asserts the desired behavior and is + `xfail(strict=True)`. + +### VRF-ROUTER-DETACH-01 — VRF router detach syncs bound baremetal port physnets +- given: a VXLAN network with baremetal ports on two physnets and an attached + VRF router interface +- when: the router interface is removed +- then: undersync should sync each physnet still carrying baremetal ports +- status: **KNOWN BUG (xfail, rackerlabs/understack#2240)** — the teardown + counterpart of VRF-ROUTER-ATTACH-01; detach does not sync those physnets today. + +### SVI-ROUTER-ATTACH-01 — SVI router attach syncs bound baremetal port physnets +- given: a VXLAN network + an address-scoped IPv4 subnet, with baremetal ports + bound to two different physnets (`physnet1`, `physnet2`) +- when: an SVI router is created and the subnet is attached on the internal side +- then: undersync syncs each physnet carrying the network's baremetal ports so + the switches are reconciled for the new router +- status: **KNOWN BUG (xfail, rackerlabs/understack#2240)** — the same gap as + VRF-ROUTER-ATTACH-01 for the SVI flavor: attaching the SVI router interface does not + sync those physnets today. The SVI flavor is simulated by patching + `_router_has_flavor` and `svi._is_svi_router`; the subnet is address-scoped so + the SVI precommit scope validation passes. + +### SVI-VAL-NOSCOPE-01 — SVI rejects a subnet with no address scope +- given: an SVI router and a subnet not in any address scope +- when: the subnet is attached on the internal side +- then: the attach is rejected (the SVI precommit validator raises BadRequest, + which the ML2 manager surfaces as MechanismDriverError) + +### SVI-VAL-IPV6-01 — SVI rejects an IPv6 subnet +- given: an SVI router and an IPv6 subnet +- when: the subnet is attached +- then: the attach is rejected (SVI routers are IPv4-only) + +### SVI-VAL-CONFLICT-01 — SVI rejects conflicting address scopes +- given: an SVI router with an interface in address scope A +- when: a subnet in a different scope B is attached +- then: the attach is rejected (per-IP-version scope conflict) + +## Router uplink (non-flavored, OVN) + +These load an L3 router + flavors + trunk plugin +(`UnderstackMl2RouterOvnScenarioBase`) and patch `routers.ovn_client` with a +`FakeOvnClient` (records localnet LSP create/delete; short-circuits the vxlan +HCG workaround). `utils.fetch_network_node_trunk_id` is mocked to a trunk the +test creates. + +### OVN-ROUTER-ATTACH-01 — non-flavored router attach builds the uplink +- given: a network+subnet, a network-node trunk, and a non-flavored router +- when: the subnet is attached on the internal side +- then: a VLAN uplink segment marked dynamic is allocated on the network-node + physnet; the shared `uplink-` neutron port, trunk subport tag, and OVN localnet + LSP tag all reference that segment and VLAN on the network's logical switch + +### OVN-ROUTER-SECOND-01 — second router on the same network is a no-op +- given: a network with two subnets, the first already attached to a router +- when: a second router attaches the second subnet +- then: no new uplink is built (`is_only_router_port_on_network` is false) + +### OVN-ROUTER-DETACH-01 — remove_router_interface tears down the uplink +- given: a network with a router interface and its uplink +- when: the interface is removed +- then: the shared port is removed from the network-node trunk, both the + `uplink-<segment-id>` localnet LSP and shared-port LSP are deleted from the + exact logical switch, the shared `uplink-` neutron port is removed, and the + dynamic segment is released +- status: **KNOWN BUG (xfail, rackerlabs/understack#2245)** — teardown deletes + the ports but leaks the dynamic uplink VLAN segment today. The test asserts + the desired release and is `xfail(strict=True)`; a separate fix branch + resolves it (flipping this to a pass once merged). + +## Router flavor providers (Palo Alto) + +These register the Palo Alto provider as an L3 service provider and create a real +flavor + service profile (`driver` = the PaloAlto class, `metainfo.resource_class` += the netdev pool). `IronicClient` is faked (single-node pool). + +### PALO-ROUTER-ADOPT-01 — Palo Alto router adopts an Ironic netdev node +- given: the Palo Alto provider registered and a matching flavor +- when: a router with that flavor is created +- then: the ROUTER BEFORE_CREATE callback requests a node using the service + profile's resource class and adopts it with the router's project, ID, and name + (via the faked Ironic client) + +### PALO-ROUTER-RELEASE-01 — deleting a Palo Alto router releases its node +- given: a Palo Alto router that adopted a node +- when: the router is deleted +- then: the ROUTER AFTER_DELETE callback returns the node to the pool + +## Known bugs (surfaced by these tests) + +- **Dynamic VLAN segment leaks on vif-detach** (BM-BIND-04, `xfail`, + rackerlabs/understack#2239). On the + bound→unbound update, `_tenant_network_port_cleanup` releases + `original_top_bound_segment` — the VXLAN segment, which is not dynamic — instead + of the dynamic VLAN segment at the bottom binding level. So + `release_segment_if_unused` no-ops and the VLAN segment (and its VLAN id) is + never freed on detach. It is only released on port **delete** + (`_delete_port_baremetal`, BM-BIND-05), which contradicts the comment there that + says detach "normally" releases it. Result: VLAN ids leak whenever a port is + unbound without being deleted. Fix should release the dynamic (bottom) segment + on the detach transition; the xfail flips to a pass once fixed. + +## Backlog (not yet covered) + +Proposed scenarios, grouped by area. These are intentionally plain bullets (not +`###` IDs) so the coverage check does not treat them as catalogued-but-untested; +promote a bullet to a `### <ID>` heading when its test lands. + +Port create / bind / delete (no new test doubles): +- BM-REBIND-01 — rebind on `binding:host_id` change. +- BM-DEL-NOPHYSNET-01 — delete a bound port whose profile lost `physical_network` + (early return, no sync). + +Trunk (no new test doubles): +- TRUNK-CREATE-WITH-SUBPORTS-01 — trunk created with subports present + (`trunk_created` path); confirm whether it syncs (it may not — possible gap). +- TRUNK-PARENT-UNBIND-01 — unbinding a trunked baremetal parent runs `clean_trunk` + (subport teardown) alongside BM-BIND-04's segment handling. +- TRUNK-NOPHYSNET-01 — subport add/remove on a parent with no `physical_network`. + Not reachable via normal binding (a baremetal port cannot bind without a + physnet), so needs an artificially mutated binding profile. + +SVI validation: +- SVI-EXTGW-01 — an SVI router cannot get an external gateway. Needs the real + Svi provider + flavor wiring (the `_reject_svi_external_gateway` callback), + which the patched-flavor scenarios do not load. + +Router (needs an OVN IDL fake for `routers.ovn_client()`): +- OVN-ROUTER-DELETE-01 — `delete_router` cleanup via `handle_router_interface_removal` + (PORT PRECOMMIT_DELETE). Not reachable with the standard L3 plugin, which + raises RouterInUse unless interfaces are removed first; needs a trigger that + deletes a router-interface port directly. +- OVN-ROUTER-HCG-VXLAN-01 — `link_vxlan_network_ha_chassis_group` populates the unified + HCG for a vxlan external gateway (needs a deeper OVN NB/SB fake). + +VNI: +- VNI-ALLOC-01 — VRF router create/delete allocates/releases an `evpn_vni` + (`UnderstackVniPlugin`). Needs the understack_vni service plugin loaded, its + `understack_router_vni_allocations` table created (import the model so the + SQLite fixture builds it), and a flavor whose service-profile metainfo sets + `vni_alloc`. The Vrf/UserDefined provider it pairs with may pull in OVN L3 on + router create, so it likely also needs the OVN fake. + +Cross-cutting: +- DRYRUN-01 — `undersync_dry_run=True` routes to dry-run instead of sync. This + branch lives inside `Undersync.sync()` (an HTTP-client detail) which the + scenarios mock, so it has no scenario-observable effect; better unit-tested. + +Explicitly out of scope: Cisco ASA floating-IP NAT. diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/__init__.py b/python/neutron-understack/neutron_understack/tests/scenarios/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/base.py b/python/neutron-understack/neutron_understack/tests/scenarios/base.py new file mode 100644 index 000000000..73f5af325 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/base.py @@ -0,0 +1,211 @@ +"""In-process ML2 scenario-test harness for the understack mechanism drivers. + +These tests reuse neutron's own functional test bases, which stand up a real +``Ml2Plugin`` backed by in-memory SQLite. We load our two mechanism drivers -- +``understack`` and ``undersync`` -- into the binding chain and drive real +``create_network`` / ``create_port`` / router operations through the ML2 manager, +so a scenario can assert *which* calls reach our drivers and *with what data*. +This is the same style upstream neutron uses to test its own ML2 drivers. + +``UnderstackMl2ScenarioBase`` is the plain base (port binding scenarios). +``UnderstackMl2RouterScenarioBase`` adds a real L3 router + flavors plugin +(``ML2TestFramework``) for router scenarios. + +Only the genuine external edge on the binding path -- the Undersync HTTP client -- +is mocked. Router scenarios that reach the OVN uplink path would additionally +need the OVN IDL faked; the flavored (VRF) router path skips it. +""" + +from unittest import mock + +from neutron.conf.agent import ovs_conf +from neutron.conf.plugins.ml2.drivers import driver_type +from neutron.tests.unit.plugins.ml2.base import ML2TestFramework +from neutron.tests.unit.plugins.ml2.test_plugin import Ml2PluginV2TestCase +from neutron_lib.api.definitions import portbindings +from neutron_lib.plugins import directory +from oslo_config import cfg + +from neutron_understack import config as understack_config +from neutron_understack.undersync import Undersync + +#: Physnets the type manager knows about (the parent setUp configures physnet1/2 +#: with VLAN ranges), so ``allocate_dynamic_segment`` succeeds for the VLAN group +#: named in a baremetal binding profile. +DEFAULT_PHYSNET = "physnet1" +SECOND_PHYSNET = "physnet2" + +#: Stand-in switch identity for the binding profile's local_link_information. +DEFAULT_SWITCH_ID = "11:22:33:44:55:66" +DEFAULT_SWITCH_INFO = "a1-1-1.iad3.rackspace.net" + + +def _apply_understack_ml2_overrides(): + """Config overrides the plugin needs before it loads in setup_parent(). + + Tenant networks are VXLAN (matching production); dynamic VLAN segments come + from the physnet ranges the parent setUp configures. + """ + cfg.CONF.set_override("project_network_types", ["vxlan"], group="ml2") + # ml2_type_vxlan is only registered when the vxlan type driver loads, so + # register it here before overriding its (empty) vni_ranges. + driver_type.register_ml2_drivers_vxlan_opts(cfg.CONF) + cfg.CONF.set_override("vni_ranges", ["1000:2000"], group="ml2_type_vxlan") + + # UnderstackDriver.initialize() registers these itself, but we register up + # front so we can set values the driver reads while loading. + understack_config.register_ml2_understack_opts(cfg.CONF) + cfg.CONF.set_override( + "undersync_url", "http://undersync.test", group="ml2_understack" + ) + cfg.CONF.set_override("undersync_dry_run", False, group="ml2_understack") + cfg.CONF.set_override( + "provisioning_network", + "00000000-0000-0000-0000-000000000000", + group="ml2_understack", + ) + + +class _UnderstackMl2ScenarioMixin: + """Shared setUp + helpers for understack ML2 scenario bases.""" + + # Order matters: understack does hierarchical VXLAN->VLAN binding and + # undersync finalizes the VLAN segment. 'logger' mirrors the production list. + _mechanism_drivers = ["logger", "understack", "undersync"] + + def setUp(self): + _apply_understack_ml2_overrides() + super().setUp() + # Replace the real HTTP Undersync client with a mock -- the single + # genuine external edge on the binding path. The trunk driver captured + # its own reference at construction (trunk.py), so patch that too. + self.understack_driver = self._mech_driver("understack") + self.undersync_mock = mock.MagicMock(spec_set=Undersync) + self.understack_driver.undersync = self.undersync_mock + self.understack_driver.trunk_driver.undersync = self.undersync_mock + + @staticmethod + def _mech_driver(name): + """Return the loaded mechanism driver instance registered under ``name``.""" + manager = directory.get_plugin().mechanism_manager + for ext in manager.ordered_mech_drivers: + if ext.name == name: + return ext.obj + raise AssertionError(f"mechanism driver {name!r} not loaded") + + @staticmethod + def baremetal_binding_profile(physnet=DEFAULT_PHYSNET, port_id="Ethernet1/1"): + """A baremetal port binding profile as Ironic supplies on vif-attach. + + ``physical_network`` names the VLAN group; ``local_link_information`` + identifies the switch port. Pass ``physnet=None`` to omit the + ``physical_network`` key (the "missing physnet" edge case). + """ + profile: dict = { + "local_link_information": [ + { + "port_id": port_id, + "switch_id": DEFAULT_SWITCH_ID, + "switch_info": DEFAULT_SWITCH_INFO, + } + ], + } + if physnet is not None: + profile["physical_network"] = physnet + return profile + + def _bind_baremetal_port(self, net_id, physnet, host, fixed_ips=None): + """Create a baremetal port on the network and vif-attach it to physnet. + + Returns the bound port's id. binding:host_id / binding:profile writes + require the service role under secure RBAC. Pass ``fixed_ips=[]`` to force + a port with no IP even on a subnetted network. + """ + create_kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_BAREMETAL} + if fixed_ips is not None: + create_kwargs["fixed_ips"] = fixed_ips + res = self._create_port( + self.fmt, + net_id, + arg_list=(portbindings.VNIC_TYPE,), + is_admin=True, + **create_kwargs, + ) + assert res.status_int == 201, res.body + port_id = self.deserialize(self.fmt, res)["port"]["id"] + data = { + "port": { + portbindings.HOST_ID: host, + portbindings.PROFILE: self.baremetal_binding_profile(physnet=physnet), + } + } + req = self.new_update_request("ports", data, port_id, as_service=True) + assert req.get_response(self.api).status_int == 200 + return port_id + + def _trunk_subports(self, trunk_id): + """Return a trunk's subports as the trunk plugin reports them. + + Requires ``self.trunk_plugin`` (set by the trunk/router-OVN bases). + """ + return self.trunk_plugin.get_trunk(self.context, trunk_id)["sub_ports"] + + +class UnderstackMl2ScenarioBase(_UnderstackMl2ScenarioMixin, Ml2PluginV2TestCase): + """Base for port-binding scenarios (no L3/router machinery).""" + + +class UnderstackMl2RouterScenarioBase(_UnderstackMl2ScenarioMixin, ML2TestFramework): + """Base for router scenarios: real L3RouterPlugin + flavors plugin. + + ``self.l3_plugin`` is the loaded L3 plugin and ``self._create_router()`` + creates a router directly (bypassing HTTP), both provided by ML2TestFramework. + """ + + +class UnderstackMl2RouterOvnScenarioBase(UnderstackMl2RouterScenarioBase): + """Router base for the non-flavored uplink path (OVN + network-node trunk). + + Loads the trunk plugin (the uplink adds the shared port to the network-node + trunk) and configures ``network_node_switchport_physnet``. Scenarios patch + ``routers.ovn_client`` with a FakeOvnClient and mock + ``utils.fetch_network_node_trunk_id`` to a trunk they create. + """ + + #: A VLAN physnet the parent setUp configures (physnet3: 400-500), used for + #: the router<->network-node uplink segment. + NETWORK_NODE_PHYSNET = "physnet3" + + def get_additional_service_plugins(self): + plugins = super().get_additional_service_plugins() + plugins["trunk_plugin_name"] = "neutron.services.trunk.plugin.TrunkPlugin" + return plugins + + def setUp(self): + super().setUp() + cfg.CONF.set_override( + "network_node_switchport_physnet", + self.NETWORK_NODE_PHYSNET, + group="ml2_understack", + ) + # create_uplink_port reads cfg.CONF.OVS igmp options. + ovs_conf.register_ovs_agent_opts(cfg.CONF) + self.trunk_plugin = directory.get_plugin("trunk") + + +class UnderstackMl2TrunkScenarioBase(UnderstackMl2ScenarioBase): + """Base for trunk scenarios: loads the real neutron trunk service plugin. + + The understack trunk driver registers against the trunk plugin's AFTER_INIT + event, so loading the plugin here wires the SUBPORTS/TRUNK handlers. Exposes + ``self.trunk_plugin``. + """ + + def get_additional_service_plugins(self): + plugins = super().get_additional_service_plugins() + plugins["trunk_plugin_name"] = "neutron.services.trunk.plugin.TrunkPlugin" + return plugins + + def setUp(self): + super().setUp() + self.trunk_plugin = directory.get_plugin("trunk") diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/catalog.py b/python/neutron-understack/neutron_understack/tests/scenarios/catalog.py new file mode 100644 index 000000000..f58fdf160 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/catalog.py @@ -0,0 +1,29 @@ +"""Single source of truth for parsing the SCENARIOS.md catalog. + +Both the coverage meta-test (test_scenario_coverage.py) and the CI report +(scripts/scenario-report.py) parse scenario IDs from the catalog headings; they +import from here so the heading format is defined once and cannot diverge. +""" + +import re + +SCENARIO_ID_PATTERN = r"[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+" +SCENARIO_ID_RE = re.compile(rf"^{SCENARIO_ID_PATTERN}$") + +#: Catalog IDs are declared as headings: "### <ID> — <title>" (title optional). +_CATALOG_RE = re.compile(rf"^#{{2,3}}\s+({SCENARIO_ID_PATTERN})(?:\s*[—-]\s*(.*))?$") + + +def parse_catalog(text): + """Return an ordered list of ``(scenario_id, title)`` from the catalog.""" + entries = [] + for line in text.splitlines(): + match = _CATALOG_RE.match(line) + if match: + entries.append((match.group(1), (match.group(2) or "").strip())) + return entries + + +def catalog_ids(text): + """Return an ordered list of catalogued scenario IDs.""" + return [scenario_id for scenario_id, _ in parse_catalog(text)] diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/conftest.py b/python/neutron-understack/neutron_understack/tests/scenarios/conftest.py new file mode 100644 index 000000000..ee44c1ce1 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/conftest.py @@ -0,0 +1,84 @@ +"""Scenario-test collection enforcement and JUnit tagging hooks. + +- Validates every collected scenario marker and rejects unknown catalog IDs. +- When all scenario modules are collected, fails if a catalog entry has no test. +- Stamps each scenario test's ID onto ``user_properties`` so it surfaces in the + JUnit XML as ``<property name="scenario" value="<ID>"/>``. scripts/ + scenario-report.py turns that into a scenario→status traceability matrix. +""" + +from pathlib import Path + +import pytest + +from neutron_understack.tests.scenarios.catalog import SCENARIO_ID_RE +from neutron_understack.tests.scenarios.catalog import catalog_ids + +# The catalog meta-test is not itself a scenario. +_EXEMPT_FILES = {"test_scenario_coverage.py"} +_SCENARIOS_DIR = Path(__file__).parent +_CATALOG = _SCENARIOS_DIR / "SCENARIOS.md" + + +def _scenario_id(item): + marker = item.get_closest_marker("scenario") + if marker is None: + return None, "missing an @pytest.mark.scenario('<ID>') marker" + if len(marker.args) != 1 or marker.kwargs: + return None, "scenario marker must have exactly one positional ID" + + scenario_id = marker.args[0] + if not isinstance(scenario_id, str) or not SCENARIO_ID_RE.fullmatch(scenario_id): + return None, f"invalid scenario ID {scenario_id!r}" + return scenario_id, None + + +def pytest_collection_modifyitems(config, items): + errors = [] + collected_ids = set() + collected_files = set() + for item in items: + item_path = Path(str(getattr(item, "fspath", ""))).resolve() + path = item_path.as_posix() + if "/tests/scenarios/" not in path: + continue + if item_path.name in _EXEMPT_FILES: + continue + + collected_files.add(item_path) + scenario_id, error = _scenario_id(item) + if error: + errors.append(f"{item.nodeid}: {error}") + continue + collected_ids.add(scenario_id) + item.user_properties.append(("scenario", scenario_id)) + + catalog = catalog_ids(_CATALOG.read_text()) + unknown = sorted(collected_ids - set(catalog)) + if unknown: + errors.append(f"scenario IDs missing from SCENARIOS.md: {unknown}") + + scenario_files = { + scenario_path.resolve() + for scenario_path in _SCENARIOS_DIR.glob("test_*.py") + if scenario_path.name not in _EXEMPT_FILES + } + invocation_dir = Path(config.invocation_params.dir) + requested_paths = { + (invocation_dir / str(arg).split("::", 1)[0]).resolve() + for arg in config.args + if not str(arg).startswith("-") + } + full_scenario_scope = any( + _SCENARIOS_DIR.resolve().is_relative_to(requested_path) + for requested_path in requested_paths + ) + if full_scenario_scope or (scenario_files and scenario_files <= collected_files): + untested = sorted(set(catalog) - collected_ids) + if untested: + errors.append(f"catalogued scenarios with no collected test: {untested}") + + if errors: + raise pytest.UsageError( + "scenario collection validation failed:\n " + "\n ".join(errors) + ) diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/fakes.py b/python/neutron-understack/neutron_understack/tests/scenarios/fakes.py new file mode 100644 index 000000000..7db5a568e --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/fakes.py @@ -0,0 +1,87 @@ +"""In-harness fakes for external systems the router path reaches into. + +The router uplink path (``neutron_understack.routers``) talks to the OVN +Northbound IDL via ``routers.ovn_client()``. These fakes record the localnet +port operations and make the vxlan-HCG workaround short-circuit, so router +scenarios can run without a real OVN database. +""" + +import contextlib +from unittest import mock + + +class FakeNbIdl: + """Minimal OVN Northbound IDL: records localnet LSP create/delete.""" + + def __init__(self): + self.created_ports = [] + self.deleted_ports = [] + + def create_lswitch_port(self, **kwargs): + self.created_ports.append(kwargs) + return mock.MagicMock(name="create_lswitch_port_cmd") + + def delete_lswitch_port(self, **kwargs): + self.deleted_ports.append(kwargs) + return mock.MagicMock(name="delete_lswitch_port_cmd") + + def lookup(self, table, name, default=None): + # Report the per-network HCG as already populated so + # link_vxlan_network_ha_chassis_group returns early (that workaround has + # its own dedicated scenario; the uplink scenarios don't exercise it). + if table == "HA_Chassis_Group": + return mock.MagicMock(ha_chassis=[mock.MagicMock()]) + return default + + def db_list_rows(self, table): + cmd = mock.MagicMock() + cmd.execute.return_value = [] + return cmd + + def transaction(self, **kwargs): + return contextlib.nullcontext(mock.MagicMock()) + + +class FakeOvnClient: + """Stand-in for the OVN ML2 driver's ``_ovn_client``.""" + + def __init__(self): + self._nb_idl = FakeNbIdl() + self._sb_idl = mock.MagicMock() + + def _transaction(self, commands, txn=None): + return None + + +class FakeIronicClient: + """Stand-in for neutron_understack.ironic.IronicClient (netdev pool). + + Models a single-node pool: one node is available until adopted, then the + router->node mapping is remembered so release can return it. Records the + adopt/release calls for assertions. + """ + + def __init__(self, node_id="netdev-node-1"): + self._node = mock.MagicMock(id=node_id) + self._available = True + self._by_router = {} + self.adopted = [] + self.released = [] + + def available_node_for_resource_class(self, resource_class): + return self._node if self._available else None + + def adopt_node_for_router(self, node, project_id, router_id, router_name): + self._available = False + self._by_router[router_id] = node + self.adopted.append( + {"node": node, "project_id": project_id, "router_id": router_id} + ) + return node + + def release_node_for_router(self, router_id): + node = self._by_router.pop(router_id, None) + if node is not None: + self._available = True + self.released.append(router_id) + return node diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_baremetal_binding.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_baremetal_binding.py new file mode 100644 index 000000000..a577851ba --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_baremetal_binding.py @@ -0,0 +1,248 @@ +"""Scenario tests for baremetal (Ironic) port binding through the ML2 chain. + +See neutron_understack/tests/scenarios/SCENARIOS.md for the catalog these tests +implement. Each test is tagged with its scenario ID via @pytest.mark.scenario. +""" + +import pytest +from neutron.db import segments_db +from neutron.plugins.ml2 import db as ml2_db +from neutron_lib import constants as p_const +from neutron_lib.api.definitions import portbindings +from oslo_config import cfg + +from neutron_understack.tests.scenarios.base import DEFAULT_PHYSNET +from neutron_understack.tests.scenarios.base import UnderstackMl2ScenarioBase + +HOST = "host-1" + + +class TestBaremetalBinding(UnderstackMl2ScenarioBase): + def _make_vxlan_network(self): + return self._make_network(self.fmt, "vxlan-net", True)["network"] + + def _create_unbound_baremetal_port(self, net_id): + res = self._create_port( + self.fmt, + net_id, + arg_list=(portbindings.VNIC_TYPE,), + is_admin=True, + **{portbindings.VNIC_TYPE: portbindings.VNIC_BAREMETAL}, + ) + assert res.status_int == 201, res.body + return self.deserialize(self.fmt, res)["port"] + + def _vif_attach(self, port_id, physnet=DEFAULT_PHYSNET, host=HOST): + """Simulate the Ironic vif-attach: update the port with host + profile.""" + data = { + "port": { + portbindings.HOST_ID: host, + portbindings.PROFILE: self.baremetal_binding_profile(physnet=physnet), + } + } + # binding:host_id / binding:profile writes require the service role + # under secure RBAC (this is the identity Ironic/Nova use). + req = self.new_update_request("ports", data, port_id, as_service=True) + res = req.get_response(self.api) + assert res.status_int == 200, res.body + return self.deserialize(self.fmt, res)["port"] + + @pytest.mark.scenario("BM-BIND-01") + def test_baremetal_vif_attach_binds_hierarchically(self): + net = self._make_vxlan_network() + port = self._create_unbound_baremetal_port(net["id"]) + port_id = port["id"] + # A freshly created baremetal port with no host is unbound. + assert port[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_UNBOUND + + updated = self._vif_attach(port_id) + + # undersync finalized the binding to VIF_TYPE_OTHER. + assert updated[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_OTHER + + # Two hierarchical binding levels: understack bound the VXLAN segment + # (level 0, via continue_binding) and handed a dynamic VLAN segment to + # undersync, which bound it (level 1, via set_binding). + levels = ml2_db.get_binding_level_objs(self.context, port_id, HOST) + assert len(levels) == 2, levels + assert levels[0].driver == "understack" + assert levels[1].driver == "undersync" + + # Top segment is VXLAN (the tenant network segment). + top = segments_db.get_segment_by_id(self.context, levels[0].segment_id) + assert top[segments_db.NETWORK_TYPE] == p_const.TYPE_VXLAN + + # Bottom segment is a *dynamically allocated* VLAN segment on our physnet. + bottom = segments_db.get_segment_by_id(self.context, levels[1].segment_id) + assert bottom[segments_db.NETWORK_TYPE] == p_const.TYPE_VLAN + assert bottom[segments_db.PHYSICAL_NETWORK] == DEFAULT_PHYSNET + + # The switch reconcile fired for the port's VLAN group. + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + def _bottom_segment_id(self, port_id, host=HOST): + levels = ml2_db.get_binding_level_objs(self.context, port_id, host) + return levels[-1].segment_id + + @pytest.mark.scenario("BM-BIND-REUSE-01") + def test_second_port_same_physnet_reuses_dynamic_segment(self): + """A 2nd baremetal port on the same network+physnet reuses the VLAN seg.""" + net = self._make_vxlan_network() + port_a = self._create_unbound_baremetal_port(net["id"]) + self._vif_attach(port_a["id"], host="host-a") + port_b = self._create_unbound_baremetal_port(net["id"]) + self._vif_attach(port_b["id"], host="host-b") + + seg_a = self._bottom_segment_id(port_a["id"], "host-a") + seg_b = self._bottom_segment_id(port_b["id"], "host-b") + assert seg_a == seg_b, (seg_a, seg_b) + + @pytest.mark.scenario("PROV-BIND-01") + def test_provisioning_network_port_binds(self): + """A baremetal port on the provisioning network binds hierarchically.""" + net = self._make_vxlan_network() + cfg.CONF.set_override("provisioning_network", net["id"], group="ml2_understack") + port = self._create_unbound_baremetal_port(net["id"]) + + updated = self._vif_attach(port["id"]) + + assert updated[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_OTHER + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("PROV-DEL-01") + def test_provisioning_network_delete_retains_segment(self): + """Deleting a provisioning-network port syncs but keeps the VLAN segment. + + The clean/provision cycle re-uses the segment, so unlike a tenant port + (BM-BIND-05) the dynamic VLAN segment is not released on delete. + """ + net = self._make_vxlan_network() + cfg.CONF.set_override("provisioning_network", net["id"], group="ml2_understack") + port = self._create_unbound_baremetal_port(net["id"]) + self._vif_attach(port["id"]) + vlan_segment_id = self._bottom_segment_id(port["id"]) + self.undersync_mock.reset_mock() + + self._delete("ports", port["id"], as_admin=True) + + # Sync fired, but the segment is retained (provisioning cycle). + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + assert segments_db.get_segment_by_id(self.context, vlan_segment_id) is not None + + @pytest.mark.scenario("BM-BIND-06") + def test_vif_attach_without_ip_still_syncs(self): + """A bound baremetal port with no IP still emits the physnet sync.""" + net = self._make_vxlan_network() + # Subnet exists on the network, but the port is created with no IP. + self._make_subnet( + self.fmt, {"network": net}, gateway="10.8.0.1", cidr="10.8.0.0/24" + ) + res = self._create_port( + self.fmt, + net["id"], + arg_list=(portbindings.VNIC_TYPE,), + is_admin=True, + **{portbindings.VNIC_TYPE: portbindings.VNIC_BAREMETAL, "fixed_ips": []}, + ) + assert res.status_int == 201, res.body + port = self.deserialize(self.fmt, res)["port"] + assert port["fixed_ips"] == [] + + updated = self._vif_attach(port["id"]) + + assert updated[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_OTHER + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("BM-BIND-02") + def test_vif_attach_without_physical_network_refuses_binding(self): + """A binding profile missing physical_network cannot be bound.""" + net = self._make_vxlan_network() + port = self._create_unbound_baremetal_port(net["id"]) + + # physnet=None omits physical_network from the profile. + updated = self._vif_attach(port["id"], physnet=None) + + assert updated[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_BINDING_FAILED + assert ml2_db.get_binding_level_objs(self.context, port["id"], HOST) == [] + self.undersync_mock.sync.assert_not_called() + + @pytest.mark.scenario("BM-BIND-03") + def test_unsupported_vnic_type_is_not_bound_by_us(self): + """A non-baremetal/normal vnic_type is refused by our drivers.""" + net = self._make_vxlan_network() + res = self._create_port( + self.fmt, + net["id"], + arg_list=(portbindings.VNIC_TYPE,), + is_admin=True, + **{portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT}, + ) + assert res.status_int == 201, res.body + port = self.deserialize(self.fmt, res)["port"] + + updated = self._vif_attach(port["id"]) + + assert updated[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_BINDING_FAILED + self.undersync_mock.sync.assert_not_called() + + # BUG rackerlabs/understack#2239: the dynamic VLAN segment leaks on + # vif-detach -- see SCENARIOS.md "Known bugs". This test asserts the *desired* + # behavior and currently fails. strict=True makes an unexpected pass fail the + # run, prompting removal of the xfail once the driver is fixed. + @pytest.mark.xfail( + strict=True, + reason=( + "understack#2239: dynamic VLAN segment leaks on vif-detach; " + "_tenant_network_port_cleanup releases original_top_bound_segment " + "(the non-dynamic VXLAN segment) instead of the dynamic VLAN segment" + ), + ) + @pytest.mark.scenario("BM-BIND-04") + def test_vif_detach_releases_segment_and_reconciles(self): + """Vif detach should unbind, reconcile the switch, AND release the VLAN. + + The bound->unbound transition must free the dynamic VLAN segment so its + VLAN id is not leaked back to the pool. Today the driver reconciles + (undersync.sync) but retains the segment (see the xfail reason). + """ + net = self._make_vxlan_network() + port = self._create_unbound_baremetal_port(net["id"]) + port_id = port["id"] + self._vif_attach(port_id) + + levels = ml2_db.get_binding_level_objs(self.context, port_id, HOST) + vlan_segment_id = levels[1].segment_id + self.undersync_mock.reset_mock() + + # vif detach: clear host + profile. + data = {"port": {portbindings.HOST_ID: "", portbindings.PROFILE: {}}} + req = self.new_update_request("ports", data, port_id, as_service=True) + res = req.get_response(self.api) + assert res.status_int == 200, res.body + updated = self.deserialize(self.fmt, res)["port"] + + assert updated[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_UNBOUND + # The switch is reconciled for the port's VLAN group ... + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + # ... and the dynamic VLAN segment is released (no port uses it anymore). + assert segments_db.get_segment_by_id(self.context, vlan_segment_id) is None + + @pytest.mark.scenario("BM-BIND-05") + def test_port_delete_releases_dynamic_vlan_segment(self): + """Deleting a bound baremetal port releases its dynamic VLAN segment.""" + net = self._make_vxlan_network() + port = self._create_unbound_baremetal_port(net["id"]) + port_id = port["id"] + self._vif_attach(port_id) + + levels = ml2_db.get_binding_level_objs(self.context, port_id, HOST) + vlan_segment_id = levels[1].segment_id + assert segments_db.get_segment_by_id(self.context, vlan_segment_id) is not None + self.undersync_mock.reset_mock() + + self._delete("ports", port_id, as_admin=True) + + # The dynamic VLAN segment is released now that no port uses it ... + assert segments_db.get_segment_by_id(self.context, vlan_segment_id) is None + # ... and the switch is reconciled for that VLAN group. + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_palo_alto_router.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_palo_alto_router.py new file mode 100644 index 000000000..0ac755828 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_palo_alto_router.py @@ -0,0 +1,109 @@ +"""Scenario tests for the Palo Alto router flavor (Ironic netdev adoption). + +See neutron_understack/tests/scenarios/SCENARIOS.md (PALO-*) for the catalog. +""" + +import json +from unittest import mock + +import pytest +from neutron_lib.plugins import constants as plugin_constants +from neutron_lib.plugins import directory +from oslo_config import cfg + +from neutron_understack.tests.scenarios.base import UnderstackMl2RouterScenarioBase +from neutron_understack.tests.scenarios.fakes import FakeIronicClient + +PALO_DRIVER = "neutron_understack.l3_router.palo_alto.PaloAlto" + + +class TestPaloAltoRouter(UnderstackMl2RouterScenarioBase): + def setUp(self): + # Register the Palo Alto provider so its ROUTER callbacks are wired. + cfg.CONF.set_override( + "service_provider", + [f"L3_ROUTER_NAT:palo_alto:{PALO_DRIVER}:default"], + group="service_providers", + ) + super().setUp() + self.flavor_plugin = directory.get_plugin(plugin_constants.FLAVORS) + + def _palo_flavor(self, resource_class="netdev-fw"): + profile = self.flavor_plugin.create_service_profile( + self.context, + { + "service_profile": { + "driver": PALO_DRIVER, + "metainfo": json.dumps({"resource_class": resource_class}), + "enabled": True, + "description": "palo alto", + } + }, + ) + flavor = self.flavor_plugin.create_flavor( + self.context, + { + "flavor": { + "name": "palo-alto", + "service_type": plugin_constants.L3, + "enabled": True, + "description": "palo alto flavor", + } + }, + ) + self.flavor_plugin.create_flavor_service_profile( + self.context, + {"service_profile": {"id": profile["id"]}}, + flavor["id"], + ) + return flavor["id"] + + @pytest.mark.scenario("PALO-ROUTER-ADOPT-01") + def test_palo_alto_router_adopts_ironic_node(self): + fake_ironic = FakeIronicClient() + flavor_id = self._palo_flavor() + + with mock.patch( + "neutron_understack.l3_router.palo_alto.IronicClient", + return_value=fake_ironic, + ): + router = self.l3_plugin.create_router( + self.context, + { + "router": { + "name": "pa-router", + "admin_state_up": True, + "project_id": self._project_id, + "flavor_id": flavor_id, + } + }, + ) + + # The router adopted the single available netdev node. + assert len(fake_ironic.adopted) == 1, fake_ironic.adopted + assert fake_ironic.adopted[0]["router_id"] == router["id"] + + @pytest.mark.scenario("PALO-ROUTER-RELEASE-01") + def test_palo_alto_router_delete_releases_ironic_node(self): + fake_ironic = FakeIronicClient() + flavor_id = self._palo_flavor() + + with mock.patch( + "neutron_understack.l3_router.palo_alto.IronicClient", + return_value=fake_ironic, + ): + router = self.l3_plugin.create_router( + self.context, + { + "router": { + "name": "pa-router", + "admin_state_up": True, + "project_id": self._project_id, + "flavor_id": flavor_id, + } + }, + ) + self.l3_plugin.delete_router(self.context, router["id"]) + + # The adopted node was returned to the pool on delete. + assert fake_ironic.released == [router["id"]], fake_ironic.released diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_router_uplink.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_router_uplink.py new file mode 100644 index 000000000..566c98022 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_router_uplink.py @@ -0,0 +1,183 @@ +"""Scenario tests for the non-flavored router uplink path (OVN + trunk). + +See neutron_understack/tests/scenarios/SCENARIOS.md (OVN-ROUTER-*) for the catalog. +""" + +import contextlib +from unittest import mock + +import pytest +from neutron.common.ovn import utils as ovn_utils +from neutron.db import segments_db +from neutron.objects.network import NetworkSegment +from neutron_lib import constants as p_const + +from neutron_understack.tests.scenarios.base import UnderstackMl2RouterOvnScenarioBase +from neutron_understack.tests.scenarios.fakes import FakeOvnClient + + +class TestRouterUplink(UnderstackMl2RouterOvnScenarioBase): + def _plain_port(self, net_id): + res = self._create_port(self.fmt, net_id, is_admin=True) + assert res.status_int == 201, res.body + return self.deserialize(self.fmt, res)["port"]["id"] + + def _make_network_node_trunk(self): + """Create a stand-in network-node trunk and return its id.""" + nn_net = self._make_network(self.fmt, "nn-net", True)["network"]["id"] + nn_parent = self._plain_port(nn_net) + trunk = self.trunk_plugin.create_trunk( + self.context, + { + "trunk": { + "port_id": nn_parent, + "project_id": self._project_id, + "admin_state_up": True, + "sub_ports": [], + } + }, + ) + return trunk["id"] + + def _router_net_with_subnet(self, name="router-net"): + net = self._make_network(self.fmt, name, True) + subnet = self._make_subnet( + self.fmt, net, gateway="10.20.0.1", cidr="10.20.0.0/24" + )["subnet"] + return net["network"]["id"], subnet["id"] + + @contextlib.contextmanager + def _ovn(self, fake, nn_trunk_id): + with ( + mock.patch("neutron_understack.routers.ovn_client", return_value=fake), + mock.patch( + "neutron_understack.utils.fetch_network_node_trunk_id", + return_value=nn_trunk_id, + ), + ): + yield + + def _shared_uplink_ports(self, net_id): + ports = self.core_plugin.get_ports( + self.context, filters={"network_id": [net_id]} + ) + return [p for p in ports if (p.get("name") or "").startswith("uplink-")] + + @pytest.mark.scenario("OVN-ROUTER-ATTACH-01") + def test_nonflavored_router_attach_builds_uplink(self): + nn_trunk_id = self._make_network_node_trunk() + net_id, subnet_id = self._router_net_with_subnet() + fake = FakeOvnClient() + router = self._create_router() + + with self._ovn(fake, nn_trunk_id): + self.l3_plugin.add_router_interface( + self.context, router["id"], {"subnet_id": subnet_id} + ) + + shared_ports = self._shared_uplink_ports(net_id) + assert len(shared_ports) == 1 + shared_port = shared_ports[0] + segment_id = shared_port["name"].removeprefix("uplink-") + segment = segments_db.get_segment_by_id(self.context, segment_id) + assert segment[segments_db.NETWORK_TYPE] == p_const.TYPE_VLAN + assert segment[segments_db.PHYSICAL_NETWORK] == self.NETWORK_NODE_PHYSNET + segment_obj = NetworkSegment.get_object(self.context, id=segment_id) + assert segment_obj.is_dynamic + + assert self._trunk_subports(nn_trunk_id) == [ + { + "port_id": shared_port["id"], + "segmentation_type": p_const.TYPE_VLAN, + "segmentation_id": segment[segments_db.SEGMENTATION_ID], + } + ] + + assert len(fake._nb_idl.created_ports) == 1, fake._nb_idl.created_ports + created = fake._nb_idl.created_ports[0] + assert created["lswitch_name"] == ovn_utils.ovn_name(net_id) + assert created["lport_name"] == shared_port["name"] + assert created["type"] == "localnet" + assert created["tag"] == segment[segments_db.SEGMENTATION_ID] + assert created["options"]["network_name"] == self.NETWORK_NODE_PHYSNET + + @pytest.mark.scenario("OVN-ROUTER-SECOND-01") + def test_second_router_on_network_is_noop(self): + nn_trunk_id = self._make_network_node_trunk() + # One network, two subnets (routers can't share a subnet's gateway IP). + net = self._make_network(self.fmt, "router-net", True) + net_id = net["network"]["id"] + subnet_a = self._make_subnet( + self.fmt, net, gateway="10.20.0.1", cidr="10.20.0.0/24" + )["subnet"] + subnet_b = self._make_subnet( + self.fmt, net, gateway="10.21.0.1", cidr="10.21.0.0/24" + )["subnet"] + fake = FakeOvnClient() + router_a = self._create_router() + router_b = self._create_router() + + with self._ovn(fake, nn_trunk_id): + self.l3_plugin.add_router_interface( + self.context, router_a["id"], {"subnet_id": subnet_a["id"]} + ) + self.l3_plugin.add_router_interface( + self.context, router_b["id"], {"subnet_id": subnet_b["id"]} + ) + + # Only the first router built the uplink; the second is a no-op. + assert len(fake._nb_idl.created_ports) == 1, fake._nb_idl.created_ports + assert len(self._shared_uplink_ports(net_id)) == 1 + + # BUG: teardown deletes the OVN LSPs and shared port but leaks the dynamic + # uplink VLAN segment -- see SCENARIOS.md "Known bugs". Asserts the desired + # release; strict=True flips to a failure once the fix lands. + @pytest.mark.xfail( + strict=True, + reason=( + "understack#2245: router uplink teardown does not release the " + "dynamic VLAN segment on the network-node physnet" + ), + ) + @pytest.mark.scenario("OVN-ROUTER-DETACH-01") + def test_remove_router_interface_tears_down_uplink(self): + nn_trunk_id = self._make_network_node_trunk() + net_id, subnet_id = self._router_net_with_subnet() + fake = FakeOvnClient() + router = self._create_router() + + with ( + self._ovn(fake, nn_trunk_id), + mock.patch.object( + self.trunk_plugin, + "remove_subports", + wraps=self.trunk_plugin.remove_subports, + ) as remove_subports, + ): + self.l3_plugin.add_router_interface( + self.context, router["id"], {"subnet_id": subnet_id} + ) + shared_port = self._shared_uplink_ports(net_id)[0] + segment_id = shared_port["name"].removeprefix("uplink-") + self.l3_plugin.remove_router_interface( + self.context, router["id"], {"subnet_id": subnet_id} + ) + + remove_subports.assert_called_once_with( + context=mock.ANY, + trunk_id=nn_trunk_id, + subports={"sub_ports": [{"port_id": shared_port["id"]}]}, + ) + assert self._trunk_subports(nn_trunk_id) == [] + assert self._shared_uplink_ports(net_id) == [] + assert segments_db.get_segment_by_id(self.context, segment_id) is None + assert fake._nb_idl.deleted_ports == [ + { + "lport_name": f"uplink-{segment_id}", + "lswitch_name": ovn_utils.ovn_name(net_id), + }, + { + "lport_name": shared_port["id"], + "lswitch_name": ovn_utils.ovn_name(net_id), + }, + ] diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_scenario_coverage.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_scenario_coverage.py new file mode 100644 index 000000000..076c83dff --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_scenario_coverage.py @@ -0,0 +1,54 @@ +"""Validate properties of the human-readable scenario catalog. + +The collection hook validates marker shape and checks the catalog against the +tests pytest actually collects. That avoids source scanning that can count a +marker in a comment or dead code as an implemented scenario. +""" + +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from neutron_understack.tests.scenarios.catalog import catalog_ids +from neutron_understack.tests.scenarios.conftest import _scenario_id + +SCENARIOS_DIR = Path(__file__).parent +CATALOG = SCENARIOS_DIR / "SCENARIOS.md" + + +def _catalog_ids(): + return catalog_ids(CATALOG.read_text()) + + +def test_catalog_has_no_duplicate_ids(): + ids = _catalog_ids() + duplicates = sorted({i for i in ids if ids.count(i) > 1}) + assert not duplicates, f"duplicate scenario IDs in SCENARIOS.md: {duplicates}" + + +def _item_with_marker(marker): + return SimpleNamespace(get_closest_marker=lambda _name: marker) + + +def test_valid_scenario_marker_returns_id(): + item = _item_with_marker(pytest.mark.scenario("BM-BIND-01").mark) + assert _scenario_id(item) == ("BM-BIND-01", None) + + +@pytest.mark.parametrize( + "marker", + [ + None, + pytest.mark.scenario().mark, + pytest.mark.scenario("BM-BIND-01", "extra").mark, + pytest.mark.scenario(id="BM-BIND-01").mark, + pytest.mark.scenario(123).mark, + pytest.mark.scenario("").mark, + pytest.mark.scenario("bm-bind-01").mark, + ], +) +def test_malformed_scenario_marker_is_rejected(marker): + scenario_id, error = _scenario_id(_item_with_marker(marker)) + assert scenario_id is None + assert error diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_svi_router_interface.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_svi_router_interface.py new file mode 100644 index 000000000..2800b55a0 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_svi_router_interface.py @@ -0,0 +1,212 @@ +"""Scenario tests for SVI router interface attach vs. bound baremetal ports. + +Extends the VRF scenario (VRF-ROUTER-ATTACH-01) to the SVI router flavor. See +neutron_understack/tests/scenarios/SCENARIOS.md (SVI-*) for the catalog. +""" + +import contextlib +from unittest import mock + +import pytest +from neutron.plugins.ml2.common import exceptions as ml2_exc + +from neutron_understack.tests.scenarios.base import DEFAULT_PHYSNET +from neutron_understack.tests.scenarios.base import SECOND_PHYSNET +from neutron_understack.tests.scenarios.base import UnderstackMl2RouterScenarioBase + + +class TestSviRouterInterface(UnderstackMl2RouterScenarioBase): + @contextlib.contextmanager + def _as_svi_router(self): + """Treat the router as SVI-flavored (skip OVN uplink, run SVI checks).""" + with ( + mock.patch( + "neutron_understack.routers._router_has_flavor", return_value=True + ), + mock.patch( + "neutron_understack.l3_router.svi._is_svi_router", return_value=True + ), + ): + yield + + def _scoped_ipv4_subnet( + self, + network, + name="svi", + pool_prefix="10.0.0.0/16", + cidr="10.0.0.0/24", + gateway="10.0.0.1", + ): + """Create an IPv4 subnet in its own address scope (SVI routers need one).""" + scope = self.core_plugin.create_address_scope( + self.context, + { + "address_scope": { + "name": f"{name}-scope", + "ip_version": 4, + "shared": False, + "tenant_id": self._project_id, + } + }, + ) + pool = self.core_plugin.create_subnetpool( + self.context, + { + "subnetpool": { + "name": f"{name}-pool", + "prefixes": [pool_prefix], + "address_scope_id": scope["id"], + "default_prefixlen": 24, + "min_prefixlen": 8, + "max_prefixlen": 32, + "shared": False, + "is_default": False, + "tenant_id": self._project_id, + } + }, + ) + return self._make_subnet( + self.fmt, + network, + gateway=gateway, + cidr=cidr, + subnetpool_id=pool["id"], + )["subnet"] + + def _scoped_ipv6_subnet(self, network): + """Create an address-scoped IPv6 subnet. + + Scoped so the SVI no-scope guard cannot be what rejects it -- only the + IPv6 guard can -- which is what SVI-VAL-IPV6-01 needs to pin. + """ + scope = self.core_plugin.create_address_scope( + self.context, + { + "address_scope": { + "name": "v6-scope", + "ip_version": 6, + "shared": False, + "tenant_id": self._project_id, + } + }, + ) + pool = self.core_plugin.create_subnetpool( + self.context, + { + "subnetpool": { + "name": "v6-pool", + "prefixes": ["2001:db8::/48"], + "address_scope_id": scope["id"], + "default_prefixlen": 64, + "min_prefixlen": 48, + "max_prefixlen": 128, + "shared": False, + "is_default": False, + "tenant_id": self._project_id, + } + }, + ) + return self._make_subnet( + self.fmt, + network, + gateway="2001:db8::1", + cidr="2001:db8::/64", + ip_version=6, + subnetpool_id=pool["id"], + )["subnet"] + + # BUG: like the VRF case (#2240), attaching an SVI router interface does not + # reconcile the switches carrying the network's already-bound baremetal + # ports -- undersync is never asked to sync those physnets. Asserts the + # desired behavior; strict=True flips to a failure once fixed. + @pytest.mark.xfail( + strict=True, + reason=( + "understack#2240: SVI router interface attach does not sync the " + "physnets of baremetal ports already bound on the network" + ), + ) + @pytest.mark.scenario("SVI-ROUTER-ATTACH-01") + def test_svi_router_interface_syncs_bound_port_physnets(self): + net = self._make_network(self.fmt, "vxlan-net", True) + net_id = net["network"]["id"] + subnet = self._scoped_ipv4_subnet(net) + + # Two baremetal ports on this network, bound to different physnets. + self._bind_baremetal_port(net_id, DEFAULT_PHYSNET, "host-a") + self._bind_baremetal_port(net_id, SECOND_PHYSNET, "host-b") + + # Isolate the router attach from the sync calls made during the binds. + self.undersync_mock.reset_mock() + + # Create an SVI router and attach the scoped subnet on the internal side. + # The SVI flavor is simulated via _as_svi_router(); the scope validation + # passes because the subnet is address-scoped. + with self._as_svi_router(): + router = self._create_router() + self.l3_plugin.add_router_interface( + self.context, router["id"], {"subnet_id": subnet["id"]} + ) + + # Desired: the switches carrying the network's baremetal ports get + # reconciled, so undersync syncs each of their physnets. + synced = { + call.args[0] + for call in self.undersync_mock.sync.call_args_list + if call.args + } + assert DEFAULT_PHYSNET in synced + assert SECOND_PHYSNET in synced + + def _attach_expecting_reject(self, subnet_id, router_id): + # The SVI scope validator raises BadRequest in create_port_precommit, + # which the ML2 manager wraps in MechanismDriverError. + with self._as_svi_router(), pytest.raises(ml2_exc.MechanismDriverError): + self.l3_plugin.add_router_interface( + self.context, router_id, {"subnet_id": subnet_id} + ) + + @pytest.mark.scenario("SVI-VAL-NOSCOPE-01") + def test_svi_rejects_subnet_without_address_scope(self): + net = self._make_network(self.fmt, "n", True) + subnet = self._make_subnet( + self.fmt, net, gateway="10.5.0.1", cidr="10.5.0.0/24" + )["subnet"] + router = self._create_router() + self._attach_expecting_reject(subnet["id"], router["id"]) + + @pytest.mark.scenario("SVI-VAL-IPV6-01") + def test_svi_rejects_ipv6_subnet(self): + # Address-scoped so only the IPv6 guard (not the no-scope guard) can + # reject it -- otherwise this would not actually pin IPv6 rejection. + net = self._make_network(self.fmt, "n", True) + subnet = self._scoped_ipv6_subnet(net) + router = self._create_router() + self._attach_expecting_reject(subnet["id"], router["id"]) + + @pytest.mark.scenario("SVI-VAL-CONFLICT-01") + def test_svi_rejects_conflicting_address_scopes(self): + net_a = self._make_network(self.fmt, "net-a", True) + subnet_a = self._scoped_ipv4_subnet( + net_a, + name="a", + pool_prefix="10.1.0.0/16", + cidr="10.1.0.0/24", + gateway="10.1.0.1", + ) + net_b = self._make_network(self.fmt, "net-b", True) + subnet_b = self._scoped_ipv4_subnet( + net_b, + name="b", + pool_prefix="10.2.0.0/16", + cidr="10.2.0.0/24", + gateway="10.2.0.1", + ) + router = self._create_router() + # First subnet (scope A) attaches cleanly. + with self._as_svi_router(): + self.l3_plugin.add_router_interface( + self.context, router["id"], {"subnet_id": subnet_a["id"]} + ) + # Second subnet has a different scope -> rejected. + self._attach_expecting_reject(subnet_b["id"], router["id"]) diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_trunk_operations.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_trunk_operations.py new file mode 100644 index 000000000..82908fe00 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_trunk_operations.py @@ -0,0 +1,259 @@ +"""Scenario tests for trunk subport operations driving undersync sync. + +See neutron_understack/tests/scenarios/SCENARIOS.md (TRUNK-*) for the catalog. +""" + +import uuid +from unittest import mock + +import pytest +from neutron.db import segments_db +from neutron.objects.network import NetworkSegment +from neutron.plugins.ml2 import db as ml2_db +from neutron.services.trunk import exceptions as trunk_exc +from neutron_lib import constants as p_const +from neutron_lib.callbacks import exceptions as cb_exc + +from neutron_understack.tests.scenarios.base import DEFAULT_PHYSNET +from neutron_understack.tests.scenarios.base import UnderstackMl2TrunkScenarioBase + +# Any VLAN in the default tenant range [1, 3799]; no NetworkSegmentRange rows +# exist in the harness, so the whole range is allowed for subports. +SUBPORT_VLAN = 1500 + +# fetch_network_node_trunk_id() does live OVN gateway + Ironic discovery; stub it +# to a non-matching id so the tenant-trunk segmentation check runs normally. +_FAKE_NN_TRUNK = str(uuid.uuid4()) + + +class TestTrunkOperations(UnderstackMl2TrunkScenarioBase): + def _plain_port(self, net_id): + res = self._create_port(self.fmt, net_id, is_admin=True) + assert res.status_int == 201, res.body + return self.deserialize(self.fmt, res)["port"]["id"] + + def _make_trunk(self, parent_id): + trunk = self.trunk_plugin.create_trunk( + self.context, + { + "trunk": { + "port_id": parent_id, + "project_id": self._project_id, + "admin_state_up": True, + "sub_ports": [], + } + }, + ) + return trunk["id"] + + def _add_subport(self, trunk_id, subport_id, seg_id=SUBPORT_VLAN): + with mock.patch( + "neutron_understack.utils.fetch_network_node_trunk_id", + return_value=_FAKE_NN_TRUNK, + ): + self.trunk_plugin.add_subports( + self.context, + trunk_id, + { + "sub_ports": [ + { + "port_id": subport_id, + "segmentation_type": "vlan", + "segmentation_id": seg_id, + } + ] + }, + ) + + def _remove_subport(self, trunk_id, subport_id): + with mock.patch( + "neutron_understack.utils.fetch_network_node_trunk_id", + return_value=_FAKE_NN_TRUNK, + ): + return self.trunk_plugin.remove_subports( + self.context, trunk_id, {"sub_ports": [{"port_id": subport_id}]} + ) + + def _assert_subport_bound(self, trunk_id, subport_id, host, seg_id=SUBPORT_VLAN): + assert { + subport["port_id"]: subport for subport in self._trunk_subports(trunk_id) + }[subport_id] == { + "port_id": subport_id, + "segmentation_type": "vlan", + "segmentation_id": seg_id, + } + + levels = ml2_db.get_binding_level_objs(self.context, subport_id, host) + assert len(levels) == 1, levels + assert levels[0].driver == "understack" + assert levels[0].level == 0 + + segment = segments_db.get_segment_by_id(self.context, levels[0].segment_id) + assert segment[segments_db.NETWORK_TYPE] == p_const.TYPE_VLAN + assert segment[segments_db.PHYSICAL_NETWORK] == DEFAULT_PHYSNET + segment_obj = NetworkSegment.get_object(self.context, id=levels[0].segment_id) + assert segment_obj.is_dynamic + return levels[0].segment_id + + def _assert_subport_unbound(self, subport_id, host, segment_id): + assert not ml2_db.get_binding_level_objs(self.context, subport_id, host) + assert segments_db.get_segment_by_id(self.context, segment_id) is None + + @pytest.mark.scenario("TRUNK-SUB-ADD") + def test_subport_add_syncs_parent_physnet(self): + parent_net = self._make_network(self.fmt, "parent-net", True)["network"]["id"] + parent_id = self._bind_baremetal_port(parent_net, DEFAULT_PHYSNET, "host-a") + subport_net = self._make_network(self.fmt, "subport-net", True)["network"]["id"] + subport_id = self._plain_port(subport_net) + trunk_id = self._make_trunk(parent_id) + + self.undersync_mock.reset_mock() + self._add_subport(trunk_id, subport_id) + + self._assert_subport_bound(trunk_id, subport_id, "host-a") + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("TRUNK-SUB-DEL") + def test_subport_remove_syncs_parent_physnet(self): + parent_net = self._make_network(self.fmt, "parent-net", True)["network"]["id"] + parent_id = self._bind_baremetal_port(parent_net, DEFAULT_PHYSNET, "host-a") + subport_net = self._make_network(self.fmt, "subport-net", True)["network"]["id"] + subport_id = self._plain_port(subport_net) + trunk_id = self._make_trunk(parent_id) + self._add_subport(trunk_id, subport_id) + segment_id = self._assert_subport_bound(trunk_id, subport_id, "host-a") + + self.undersync_mock.reset_mock() + updated_trunk = self._remove_subport(trunk_id, subport_id) + + assert updated_trunk["sub_ports"] == [] + self._assert_subport_unbound(subport_id, "host-a", segment_id) + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("TRUNK-PARENT-NOIP") + def test_subport_add_syncs_when_parent_has_no_ip(self): + # Parent network has a subnet, but the parent port is bound with no IP. + parent_net = self._make_network(self.fmt, "parent-net", True) + self._make_subnet(self.fmt, parent_net, gateway="10.9.0.1", cidr="10.9.0.0/24") + parent_id = self._bind_baremetal_port( + parent_net["network"]["id"], DEFAULT_PHYSNET, "host-a", fixed_ips=[] + ) + subport_net = self._make_network(self.fmt, "subport-net", True)["network"]["id"] + subport_id = self._plain_port(subport_net) + trunk_id = self._make_trunk(parent_id) + + self.undersync_mock.reset_mock() + self._add_subport(trunk_id, subport_id) + + self._assert_subport_bound(trunk_id, subport_id, "host-a") + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("TRUNK-DEL-01") + def test_trunk_delete_syncs_parent_physnet(self): + parent_net = self._make_network(self.fmt, "parent-net", True)["network"]["id"] + parent_id = self._bind_baremetal_port(parent_net, DEFAULT_PHYSNET, "host-a") + subport_net = self._make_network(self.fmt, "subport-net", True)["network"]["id"] + subport_id = self._plain_port(subport_net) + trunk_id = self._make_trunk(parent_id) + self._add_subport(trunk_id, subport_id) + segment_id = self._assert_subport_bound(trunk_id, subport_id, "host-a") + + self.undersync_mock.reset_mock() + with mock.patch( + "neutron_understack.utils.fetch_network_node_trunk_id", + return_value=_FAKE_NN_TRUNK, + ): + self.trunk_plugin.delete_trunk(self.context, trunk_id) + + with pytest.raises(trunk_exc.TrunkNotFound): + self.trunk_plugin.get_trunk(self.context, trunk_id) + self._assert_subport_unbound(subport_id, "host-a", segment_id) + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("TRUNK-PARENT-UNBOUND-01") + def test_subport_add_unbound_parent_no_sync(self): + # An unbound (plain) parent port: no switchport config, no sync. + parent_net = self._make_network(self.fmt, "parent-net", True)["network"]["id"] + parent_id = self._plain_port(parent_net) + subport_net = self._make_network(self.fmt, "subport-net", True)["network"]["id"] + subport_id = self._plain_port(subport_net) + trunk_id = self._make_trunk(parent_id) + + self.undersync_mock.reset_mock() + self._add_subport(trunk_id, subport_id) + + assert self._trunk_subports(trunk_id)[0]["port_id"] == subport_id + assert not ml2_db.get_binding_level_objs(self.context, subport_id, "") + assert ( + segments_db.get_dynamic_segment( + self.context, subport_net, physical_network=DEFAULT_PHYSNET + ) + is None + ) + self.undersync_mock.sync.assert_not_called() + + @pytest.mark.scenario("TRUNK-MULTI-01") + def test_multiple_subports_add_syncs(self): + parent_net = self._make_network(self.fmt, "parent-net", True)["network"]["id"] + parent_id = self._bind_baremetal_port(parent_net, DEFAULT_PHYSNET, "host-a") + sub_net_a = self._make_network(self.fmt, "sub-a", True)["network"]["id"] + sub_net_b = self._make_network(self.fmt, "sub-b", True)["network"]["id"] + subport_a = self._plain_port(sub_net_a) + subport_b = self._plain_port(sub_net_b) + trunk_id = self._make_trunk(parent_id) + + self.undersync_mock.reset_mock() + with mock.patch( + "neutron_understack.utils.fetch_network_node_trunk_id", + return_value=_FAKE_NN_TRUNK, + ): + self.trunk_plugin.add_subports( + self.context, + trunk_id, + { + "sub_ports": [ + { + "port_id": subport_a, + "segmentation_type": "vlan", + "segmentation_id": 1500, + }, + { + "port_id": subport_b, + "segmentation_type": "vlan", + "segmentation_id": 1600, + }, + ] + }, + ) + + self._assert_subport_bound(trunk_id, subport_a, "host-a", seg_id=1500) + segment_b = self._assert_subport_bound( + trunk_id, subport_b, "host-a", seg_id=1600 + ) + segment_a = ml2_db.get_binding_level_objs(self.context, subport_a, "host-a")[ + 0 + ].segment_id + assert segment_a != segment_b + self.undersync_mock.sync.assert_any_call(DEFAULT_PHYSNET) + + @pytest.mark.scenario("TRUNK-SEGID-RANGE-01") + def test_subport_segid_out_of_range_rejected(self): + parent_net = self._make_network(self.fmt, "parent-net", True)["network"]["id"] + parent_id = self._bind_baremetal_port(parent_net, DEFAULT_PHYSNET, "host-a") + subport_net = self._make_network(self.fmt, "subport-net", True)["network"]["id"] + subport_id = self._plain_port(subport_net) + trunk_id = self._make_trunk(parent_id) + + # 4000 is a valid VLAN but outside the default tenant range [1, 3799]. + # subports_added raises SubportSegmentationIDError, which the SUBPORTS + # PRECOMMIT_CREATE callback machinery re-raises as CallbackFailure. + with pytest.raises(cb_exc.CallbackFailure) as exc_info: + self._add_subport(trunk_id, subport_id, seg_id=4000) + assert "Segmentation ID" in str(exc_info.value) + assert not ml2_db.get_binding_level_objs(self.context, subport_id, "host-a") + assert ( + segments_db.get_dynamic_segment( + self.context, subport_net, physical_network=DEFAULT_PHYSNET + ) + is None + ) diff --git a/python/neutron-understack/neutron_understack/tests/scenarios/test_vrf_router_interface.py b/python/neutron-understack/neutron_understack/tests/scenarios/test_vrf_router_interface.py new file mode 100644 index 000000000..6a9472407 --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/scenarios/test_vrf_router_interface.py @@ -0,0 +1,104 @@ +"""Scenario tests for VRF router interface attach vs. bound baremetal ports. + +See neutron_understack/tests/scenarios/SCENARIOS.md (VRF-ROUTER-*) for the catalog. +""" + +from unittest import mock + +import pytest + +from neutron_understack.tests.scenarios.base import DEFAULT_PHYSNET +from neutron_understack.tests.scenarios.base import SECOND_PHYSNET +from neutron_understack.tests.scenarios.base import UnderstackMl2RouterScenarioBase + + +class TestVrfRouterInterface(UnderstackMl2RouterScenarioBase): + # BUG: attaching a VRF router interface to a network does not reconcile the + # switches carrying that network's already-bound baremetal ports -- undersync + # is never asked to sync those physnets. This test asserts the desired + # behavior and currently fails; strict=True flips it to a failure (prompting + # removal of the xfail) once the driver syncs on router attach. + @pytest.mark.xfail( + strict=True, + reason=( + "understack#2240: VRF router interface attach does not sync the " + "physnets of baremetal ports already bound on the network" + ), + ) + @pytest.mark.scenario("VRF-ROUTER-ATTACH-01") + def test_vrf_router_interface_syncs_bound_port_physnets(self): + net = self._make_network(self.fmt, "vxlan-net", True) + net_id = net["network"]["id"] + subnet = self._make_subnet( + self.fmt, net, gateway="10.0.0.1", cidr="10.0.0.0/24" + )["subnet"] + + # Two baremetal ports on this network, bound to different physnets. + self._bind_baremetal_port(net_id, DEFAULT_PHYSNET, "host-a") + self._bind_baremetal_port(net_id, SECOND_PHYSNET, "host-b") + + # Isolate the router attach from the sync calls made during the binds. + self.undersync_mock.reset_mock() + + # Create a VRF (flavored) router and attach the subnet on the internal + # side. The flavored path is simulated by patching _router_has_flavor, + # mirroring test_routers.py; this is the branch a real VRF router takes. + with mock.patch( + "neutron_understack.routers._router_has_flavor", return_value=True + ): + router = self._create_router() + self.l3_plugin.add_router_interface( + self.context, router["id"], {"subnet_id": subnet["id"]} + ) + + # Desired: the switches carrying the network's baremetal ports get + # reconciled, so undersync syncs each of their physnets. + synced = { + call.args[0] + for call in self.undersync_mock.sync.call_args_list + if call.args + } + assert DEFAULT_PHYSNET in synced + assert SECOND_PHYSNET in synced + + # BUG: the teardown counterpart of the attach gap -- detaching a VRF router + # interface also fails to sync the network's bound baremetal physnets. + @pytest.mark.xfail( + strict=True, + reason=( + "understack#2240: VRF router interface detach does not sync the " + "physnets of baremetal ports still bound on the network" + ), + ) + @pytest.mark.scenario("VRF-ROUTER-DETACH-01") + def test_vrf_router_interface_detach_syncs_bound_port_physnets(self): + net = self._make_network(self.fmt, "vxlan-net", True) + net_id = net["network"]["id"] + subnet = self._make_subnet( + self.fmt, net, gateway="10.0.0.1", cidr="10.0.0.0/24" + )["subnet"] + self._bind_baremetal_port(net_id, DEFAULT_PHYSNET, "host-a") + self._bind_baremetal_port(net_id, SECOND_PHYSNET, "host-b") + + with mock.patch( + "neutron_understack.routers._router_has_flavor", return_value=True + ): + router = self._create_router() + self.l3_plugin.add_router_interface( + self.context, router["id"], {"subnet_id": subnet["id"]} + ) + # Isolate the detach from the attach/bind sync calls. + self.undersync_mock.reset_mock() + self.l3_plugin.remove_router_interface( + self.context, router["id"], {"subnet_id": subnet["id"]} + ) + + # Desired: detaching the router reconciles the switches still carrying + # the network's baremetal ports. + synced = { + call.args[0] + for call in self.undersync_mock.sync.call_args_list + if call.args + } + assert DEFAULT_PHYSNET in synced + assert SECOND_PHYSNET in synced diff --git a/python/neutron-understack/neutron_understack/tests/test_container_rev_lockstep.py b/python/neutron-understack/neutron_understack/tests/test_container_rev_lockstep.py new file mode 100644 index 000000000..47192549a --- /dev/null +++ b/python/neutron-understack/neutron_understack/tests/test_container_rev_lockstep.py @@ -0,0 +1,45 @@ +"""Enforce that the test env and the container build use the same neutron. + +The scenario tests' guarantee -- "validate ML2 behavior against the same neutron +the container ships" -- only holds if the git revs pinned in this package's +[tool.uv.sources] match containers/neutron/Dockerfile's ARGs. This test fails if +they drift (e.g. a partial Renovate bump), rather than relying on a comment. +""" + +import re +import tomllib +from pathlib import Path + +import pytest + +_PKG_ROOT = Path(__file__).resolve().parents[2] +_REPO_ROOT = Path(__file__).resolve().parents[4] +_DOCKERFILE = _REPO_ROOT / "containers" / "neutron" / "Dockerfile" + +# (uv.sources key, Dockerfile ARG name) +_PINS = [ + ("neutron", "NEUTRON_GIT_REF"), + ("neutron-lib", "NEUTRON_LIB_GIT_REF"), +] + + +def _uv_source_rev(name): + data = tomllib.loads((_PKG_ROOT / "pyproject.toml").read_text()) + return data["tool"]["uv"]["sources"][name]["rev"] + + +def _dockerfile_arg(text, arg): + match = re.search(rf"^ARG {arg}=(\S+)", text, re.MULTILINE) + return match.group(1) if match else None + + +@pytest.mark.parametrize(("source_key", "arg_name"), _PINS) +def test_neutron_rev_matches_container(source_key, arg_name): + if not _DOCKERFILE.exists(): + pytest.skip(f"Dockerfile not found at {_DOCKERFILE} (not a repo checkout)") + uv_rev = _uv_source_rev(source_key) + docker_rev = _dockerfile_arg(_DOCKERFILE.read_text(), arg_name) + assert uv_rev == docker_rev, ( + f"{source_key} rev drift: pyproject [tool.uv.sources]={uv_rev} but " + f"Dockerfile {arg_name}={docker_rev}. Keep them in lockstep." + ) diff --git a/python/neutron-understack/pyproject.toml b/python/neutron-understack/pyproject.toml index 953bf2772..702dda070 100644 --- a/python/neutron-understack/pyproject.toml +++ b/python/neutron-understack/pyproject.toml @@ -65,15 +65,36 @@ test = [ "pytest-cov>=7,<8", "pytest-mock>=3.14.0,<4", "sqlalchemy", + # Base classes reused from neutron.tests (Ml2PluginV2TestCase et al.) sit on + # top of neutron's own test-only stack, which is not pulled in when neutron + # is installed as a runtime dependency. + "oslotest", + "testtools", + "fixtures", + "webtest", + "testresources", + "testscenarios", ] [tool.uv] default-groups = ["test"] +# Test against the same neutron/neutron-lib the container ships (the +# understack/2026.1 fork), not PyPI. Keep these revs in lockstep with +# containers/neutron/Dockerfile (NEUTRON_GIT_REF / NEUTRON_LIB_GIT_REF). +[tool.uv.sources] +# renovate: name=openstack/neutron repo=https://github.com/rackerlabs/neutron.git branch=understack/2026.1 +neutron = { git = "https://github.com/rackerlabs/neutron.git", rev = "b0678e56e9c5a7ec2f335c4271465fc1c3202e2a" } +# renovate: name=openstack/neutron-lib repo=https://github.com/rackerlabs/neutron-lib.git branch=understack/2026.1 +neutron-lib = { git = "https://github.com/rackerlabs/neutron-lib.git", rev = "6d0367183cc92656ba449acefd830cb4e11f2f5b" } + [tool.pytest.ini_options] minversion = "6.0" -addopts = "-ra" +addopts = "-ra --strict-markers" filterwarnings = "ignore::DeprecationWarning" +markers = [ + "scenario(id): links a test to a scenario ID in tests/scenarios/SCENARIOS.md", +] [tool.ruff] # use our default and override anything we need specifically diff --git a/python/neutron-understack/uv.lock b/python/neutron-understack/uv.lock index d59082165..c748242b4 100644 --- a/python/neutron-understack/uv.lock +++ b/python/neutron-understack/uv.lock @@ -4,16 +4,16 @@ requires-python = "==3.12.*" [[package]] name = "alembic" -version = "1.17.2" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/2b/e4153978368de59918115c9e01d3ebf58a558a7285efa7e960c383c4b59a/alembic-1.19.1.tar.gz", hash = "sha256:e0fca0518118c78acc493e31bcb5402f190057aaf6df8b5b95ce94c4789cf648", size = 2070816, upload-time = "2026-08-08T16:32:01.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/20/89/e62cc37b69ad357cc8ecd6e7367f5245f523d3cbb338a66197212bdf6749/alembic-1.19.1-py3-none-any.whl", hash = "sha256:b39018cb3d9413a19cbd54cf3c02ad33998641f0538eb77413a488a21c3e14be", size = 265946, upload-time = "2026-08-08T16:32:03.153Z" }, ] [[package]] @@ -30,20 +30,20 @@ wheels = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] name = "autopage" -version = "0.5.2" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/9e/559b0cfdba9f3ed6744d8cbcdbda58880d3695c43c053a31773cefcedde3/autopage-0.5.2.tar.gz", hash = "sha256:826996d74c5aa9f4b6916195547312ac6384bac3810b8517063f293248257b72", size = 33031, upload-time = "2023-10-16T09:22:19.54Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/76/9078d8db91f29af9ac5a359757f63f2d0fa869aba704d5ef0f836db62ea1/autopage-0.6.0.tar.gz", hash = "sha256:42d07de90de63e83762828028bfd56d19906a18f7c951ef6eef3e9ad48a3071d", size = 26797, upload-time = "2026-01-30T03:42:05.676Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/63/f1c3fa431e91a52bad5e3602e9d5df6c94d8d095ac485424efa4eeddb4d2/autopage-0.5.2-py3-none-any.whl", hash = "sha256:f5eae54dd20ccc8b1ff611263fc87bc46608a9cde749bbcfc93339713a429c55", size = 30231, upload-time = "2023-10-16T09:22:17.316Z" }, + { url = "https://files.pythonhosted.org/packages/73/6c/0324d6ed15cfb3ed0d2578fd1486f815e01f7f8d0a32b1522ff3e611e5f9/autopage-0.6.0-py3-none-any.whl", hash = "sha256:87566f08a7d4ba20e346515d26ba1132f2fac4e5619ffba3079e63c28e5df98f", size = 30693, upload-time = "2026-01-30T03:42:04.449Z" }, ] [[package]] @@ -84,75 +84,104 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, ] +[[package]] +name = "beautifulsoup4" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, +] + [[package]] name = "cachetools" -version = "6.2.2" +version = "7.1.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/d2/47e8bc06fe2a06d3f5bdf20f1126ab66c4e99dc48d940e7ba873f7ac7131/cachetools-7.1.7.tar.gz", hash = "sha256:a3e2a00b14d8f8a6b70c1dae7b4685e7ad3bc965c5b42124a2d6ce895da6cf50", size = 40680, upload-time = "2026-08-01T21:20:40.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d8/767faeda872075724b95dd675466a645f1b92aadcdcf2d1429dcfd76c176/cachetools-7.1.7-py3-none-any.whl", hash = "sha256:ef98ef375ad188819ef2f9b3645e3987f4b8c5b7550e436ad998c2de78296df0", size = 16830, upload-time = "2026-08-01T21:20:38.977Z" }, ] [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } wheels = [ - { 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/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] [[package]] name = "cliff" -version = "4.12.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "autopage" }, @@ -161,25 +190,24 @@ dependencies = [ { name = "pyyaml" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/f1/e07262a8812f2553239087f65d4948883234561d2ea90a22262352be721d/cliff-4.12.0.tar.gz", hash = "sha256:3fa9f45f8b216fabbea2a4322d7b0704221455f4ce87492393925cabba743dc6", size = 86665, upload-time = "2025-11-20T15:00:52.597Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/4a/908e0d2a7d81e3a199c24b8bf787670ba4ec15105785ff3834f1d4e16a84/cliff-4.16.0.tar.gz", hash = "sha256:85314ad49bd62f90a51094d4e31b1cb4d3b92fb83cb3935eeba0236c32839e75", size = 90041, upload-time = "2026-08-24T14:04:07.949Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/81/3b9a10658286b4c6564d570862cf16733fa28b53cbe0e6f6ddb9e3594530/cliff-4.12.0-py3-none-any.whl", hash = "sha256:952c84dad995018da1515cb980acb08bdc6b627124ec94d288a0ccdbbab52080", size = 84624, upload-time = "2025-11-20T15:00:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/3a/05/4b62f48717953851b643340922c4173ad176290659d9a4a43ef0d7617e5d/cliff-4.16.0-py3-none-any.whl", hash = "sha256:6739e39df0b0c9424787964141c60eee9a533ed0bc3c364857c716fcb666f871", size = 86908, upload-time = "2026-08-24T14:04:06.859Z" }, ] [[package]] name = "cmd2" -version = "2.7.0" +version = "4.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "gnureadline", marker = "sys_platform == 'darwin'" }, + { name = "prompt-toolkit" }, { name = "pyperclip" }, - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, + { name = "rich" }, { name = "rich-argparse" }, - { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/68/4bf43d284e41c01c6011146e5c2824aa6f17a3bb1ef10ba3dbbae5cf31dc/cmd2-2.7.0.tar.gz", hash = "sha256:81d8135b46210e1d03a5a810baf859069a62214788ceeec3588f44eed86fbeeb", size = 593131, upload-time = "2025-06-30T16:54:26.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/6f/204e9f84b28fb30561403dd854aa4f3f26f530a45598a6b7d5f2e192b961/cmd2-4.2.1.tar.gz", hash = "sha256:89021603a18ceb41600d2f02b40c5df4d37e411027308e9a58714be1f3a56b6b", size = 854163, upload-time = "2026-08-22T20:16:58.41Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/83/0f65933b7daa436912173f3d63232d158b60686318fccc7cf458ff15bfe8/cmd2-2.7.0-py3-none-any.whl", hash = "sha256:c85faf603e8cfeb4302206f49c0530a83d63386b0d90ff6a957f2c816eb767d7", size = 154309, upload-time = "2025-06-30T16:54:25.039Z" }, + { url = "https://files.pythonhosted.org/packages/7b/50/ea1614261e88b649e0186a3038bad6b948777ba70833002db4c4b0b3cbdb/cmd2-4.2.1-py3-none-any.whl", hash = "sha256:1b4be8851cbc473cdb4f459af5acb0a53ef174b16f5918a378ee8a29f51efcbb", size = 193600, upload-time = "2026-08-22T20:16:56.158Z" }, ] [[package]] @@ -193,98 +221,97 @@ wheels = [ [[package]] name = "cotyledon" -version = "2.1.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setproctitle", marker = "sys_platform != 'win32'" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/15/4f47ad3c352568d5adf9aec06279b1fa3a12a02e02da343dfae0512f2403/cotyledon-2.1.0.tar.gz", hash = "sha256:ddf5d3639efd10789c9708a89890e55529b860986e7ca7d602a13c9eb15d8709", size = 26754, upload-time = "2025-08-30T18:56:04.583Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/9e/e7856beede7f8e6c5616ea0b692b279295febbdf301abb48dcd3b05424fe/cotyledon-2.2.0.tar.gz", hash = "sha256:5f9dcc4cfb8124e6550119bb4f6fbda8628bbe653804015b76c392fa5f208009", size = 29077, upload-time = "2025-12-23T14:52:54.415Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/14/baf084e49f818049e111bd221075aa54caa71be134052e880c989efd55ad/cotyledon-2.1.0-py3-none-any.whl", hash = "sha256:6b32f069300832b24a3e1a69ced003d8ed725549e38a8a98adb342864adf17cf", size = 25326, upload-time = "2025-08-30T18:56:03.247Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/fb4a45f4bdde90934955a7c4244d4ffbedccaeaaa838f29714b45a2112e2/cotyledon-2.2.0-py3-none-any.whl", hash = "sha256:130c04c339a2f94f482135c10dcfc83c7f7b4daca3c938a6101578b658b75017", size = 28566, upload-time = "2025-12-23T14:52:53.198Z" }, ] [[package]] name = "coverage" -version = "7.12.0" +version = "7.15.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, - { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, - { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, - { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, - { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, + { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3f/f0642a372f494bd0d7dad3b497083b910194a5f1c88be2c94fef707c3b59/coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2", size = 257145, upload-time = "2026-08-06T13:47:41.931Z" }, + { url = "https://files.pythonhosted.org/packages/71/17/8b46d0ed68251016002ec972c8fc0119961a765d0984cafb8bf317c43758/coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931", size = 258257, upload-time = "2026-08-06T13:47:43.527Z" }, + { url = "https://files.pythonhosted.org/packages/30/b8/8498a0e72d0adbe15477dd07463d2b3bb2c9f6a4815e8589e50939e2c3ae/coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8", size = 260517, upload-time = "2026-08-06T13:47:45.121Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/7dce19c3bdb1e3dd63e769508216500edad81bd5f69a26d724e32aceaf78/coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9", size = 254785, upload-time = "2026-08-06T13:47:46.541Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b1/e1494703c675a2561723cd9b89f45c9168782c31280c611b1f767851e57c/coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839", size = 256176, upload-time = "2026-08-06T13:47:48.155Z" }, + { url = "https://files.pythonhosted.org/packages/73/76/a5629d270fb638a43a4b10466f51e2f49d532c1aa4da2913cbbb150bbe0a/coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72", size = 254321, upload-time = "2026-08-06T13:47:49.757Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/9c44447218435d5766b911534f9d798144a5560f85e9a54ebe5f3f5d19f9/coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52", size = 258390, upload-time = "2026-08-06T13:47:51.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/36/c1e127616fb3fa18a9ff71e76c417f2fd7424332a4870015ac224ef4c039/coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c", size = 253894, upload-time = "2026-08-06T13:47:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b9/fdb92c8ae7a8bb9b850cc253b7b3b9c8526f68130002048b5671cd510d09/coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4", size = 255763, upload-time = "2026-08-06T13:47:54.296Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/a7d51b2587c7bdb76e71b0896d2565bf7d60436b5122fc83e511adb1f7cd/coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b", size = 224597, upload-time = "2026-08-06T13:47:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/49/b9/5c5f80cc55f5acaaca6dee677626bfcec8c87204a7809b438b08e84f4571/coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd", size = 225135, upload-time = "2026-08-06T13:47:57.52Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/2a4561f89ff6bf7c925c287d0f2cce8bdf139c3a33735c87e3203401cf94/coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f", size = 224515, upload-time = "2026-08-06T13:47:58.977Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, ] [[package]] name = "cryptography" -version = "46.0.3" +version = "50.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, + { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, + { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, ] [[package]] name = "debtcollector" -version = "3.0.0" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/e2/a45b5a620145937529c840df5e499c267997e85de40df27d54424a158d3c/debtcollector-3.0.0.tar.gz", hash = "sha256:2a8917d25b0e1f1d0d365d3c1c6ecfc7a522b1e9716e8a1a4a915126f7ccea6f", size = 31322, upload-time = "2024-02-22T15:39:20.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/57/1bbe02be744995408d944cf46b8c818cf072873064b1cd3c79c11618b216/debtcollector-3.1.0.tar.gz", hash = "sha256:278a45608cf16e79c0ae10851d869185c6b78f86610df8f27a451a18c1fec732", size = 32951, upload-time = "2026-03-24T10:07:38.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/ca/863ed8fa66d6f986de6ad7feccc5df96e37400845b1eeb29889a70feea99/debtcollector-3.0.0-py3-none-any.whl", hash = "sha256:46f9dacbe8ce49c47ebf2bf2ec878d50c9443dfae97cc7b8054be684e54c3e91", size = 23035, upload-time = "2024-02-22T15:39:18.99Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/3f36aed56f0e1815fdc2ed4a9f2bd680a7bfe8819f21eacded2dc00fe283/debtcollector-3.1.0-py3-none-any.whl", hash = "sha256:c64e49a66c0b71289620fc2fdf89c03d740bddb20576ddd4f04ddc01da946668", size = 24408, upload-time = "2026-03-24T10:07:37.218Z" }, ] [[package]] name = "decorator" -version = "5.2.1" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ] [[package]] @@ -311,15 +338,15 @@ wheels = [ [[package]] name = "eventlet" -version = "0.40.3" +version = "0.41.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, { name = "greenlet" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/4f/1e5227b23aa77d9ea05056b98cf0bf187cca994991060245002b640f9830/eventlet-0.40.3.tar.gz", hash = "sha256:290852db0065d78cec17a821b78c8a51cafb820a792796a354592ae4d5fceeb0", size = 565741, upload-time = "2025-08-27T09:56:16.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/e8/6a3a23a3b85ed129b21309c6eca873d833855e63879cd72b9dac20d9b76a/eventlet-0.41.2.tar.gz", hash = "sha256:721b86b77fca33a735598292022ac6feef99747bf48f52defdada6b572acd5af", size = 566270, upload-time = "2026-08-14T07:51:00.331Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/b4/981362608131dc4ee8de9fdca6a38ef19e3da66ab6a13937bd158882db91/eventlet-0.40.3-py3-none-any.whl", hash = "sha256:e681cae6ee956cfb066a966b5c0541e734cc14879bda6058024104790595ac9d", size = 364333, upload-time = "2025-08-27T09:56:10.774Z" }, + { url = "https://files.pythonhosted.org/packages/23/75/9681fa59c4b27d9e34e8be682d2fff3f5be87bf6552477f9a5d395a068c3/eventlet-0.41.2-py3-none-any.whl", hash = "sha256:6cae50e67fe6ae8bb7013e7fd4d8e0d0d20aeb9b3259b93f023c93eb6749631f", size = 364641, upload-time = "2026-08-14T07:50:58.838Z" }, ] [[package]] @@ -333,73 +360,62 @@ wheels = [ [[package]] name = "fixtures" -version = "4.2.6" +version = "4.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/ff/2d7d262a2ac563a3c976eae210057d68271f5080f0411792d8e2aa84d9a6/fixtures-4.2.6.tar.gz", hash = "sha256:95472b15b145063a672fbe33b1244ccff829fbec97d530d862d26f416d16c90b", size = 46938, upload-time = "2025-08-03T17:20:52.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/da17efe78bb35b945441170363328ad11d953d69e6273bceb668b1d68c34/fixtures-4.3.2.tar.gz", hash = "sha256:b7db64014732513f23d12b0c49ae527ba0c0c1f2667ec97d9136d67157ebce5d", size = 47173, upload-time = "2026-03-25T17:25:40.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/49/d92f66a79fe0da4b55b77597a2cfb6630c131d702a6b4a87ddd2ee277c3d/fixtures-4.2.6-py3-none-any.whl", hash = "sha256:58266d3646b7bda07ed463c6fd26a1b18a0273ee02de2b3f8ddec9e6e4359239", size = 66181, upload-time = "2025-08-03T17:20:51.155Z" }, + { url = "https://files.pythonhosted.org/packages/de/58/b7efb4b7c01cefd47efb53e50c7f1eb2cdc4aff3d98268bdc46c7f628f2c/fixtures-4.3.2-py3-none-any.whl", hash = "sha256:c71ad55a9139bd20b3265c1d44c1a67fefb9efdddd79614096f538de63fbf4d8", size = 41625, upload-time = "2026-03-25T17:25:38.898Z" }, ] [[package]] name = "futurist" -version = "3.2.1" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/12/786f4aaf9d396d67b1b7b90f248ff994e916605d0751d08a0344a4a785a6/futurist-3.2.1.tar.gz", hash = "sha256:01dd4f30acdfbb2e2eb6091da565eded82d8cbaf6c48a36cc7f73c11cfa7fb3f", size = 49326, upload-time = "2025-08-29T15:06:57.733Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/82/eb917bf6aa03197ac5bb3ce8a95bee2243a938426ec60747a52cbed9344e/futurist-3.5.0.tar.gz", hash = "sha256:97d01f6144bbe3abe1b2155cb2b42dab4ec33e09f4f7f7df7569f35d9a29aaf7", size = 61471, upload-time = "2026-08-20T15:20:26.774Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/5b/a4418215b594fa44dea7deae61fa406139e2e8acc6442d25f93d80c52c84/futurist-3.2.1-py3-none-any.whl", hash = "sha256:c76a1e7b2c6b264666740c3dffbdcf512bd9684b4b253a3068a0135b43729745", size = 40485, upload-time = "2025-08-29T15:06:56.476Z" }, -] - -[[package]] -name = "gnureadline" -version = "8.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/92/20723aa239b9a8024e6f8358c789df8859ab1085a1ae106e5071727ad20f/gnureadline-8.2.13.tar.gz", hash = "sha256:c9b9e1e7ba99a80bb50c12027d6ce692574f77a65bf57bc97041cf81c0f49bd1", size = 3224991, upload-time = "2024-10-18T14:03:11.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/bd/df8fd060e43efd3dbdd3b210bf558ce3ef854843cd093f910f4115ebe2e9/gnureadline-8.2.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c152a82613fa012ab4331bb9a0ffddb415e37561d376b910bf9e7d535607faf", size = 160504, upload-time = "2024-10-18T14:03:49.725Z" }, - { url = "https://files.pythonhosted.org/packages/97/ee/322e5340c8cdfa40e71bd0485a82404ad4cf9aed2260cca090f3c1a3a032/gnureadline-8.2.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85e362d2d0e85e45f0affae7bbfaf998b00167c55a78d31ee0f214de9ff429d2", size = 162380, upload-time = "2024-10-18T14:03:53.129Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/14c24c98b7ab804bef1bdcbbe0b6f11510366b98faba34a9cedefa309582/futurist-3.5.0-py3-none-any.whl", hash = "sha256:77c37e074fb94e2c7cc748b1b711ab4b998bf30aa76c91fbaed0206d14dd8a96", size = 50730, upload-time = "2026-08-20T15:20:25.783Z" }, ] [[package]] name = "greenlet" -version = "3.2.4" +version = "3.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/d8/7cc97c142388aef03f622e001c572c4f84e9252a439549d483f555771970/greenlet-3.5.5.tar.gz", hash = "sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c", size = 207585, upload-time = "2026-08-10T15:09:36.136Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, - { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, - { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, - { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, - { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, - { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7e/9ecd0285e3153532ae07aeb88063c43c72b4221cf0d4d123b02f3682e3ff/greenlet-3.5.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380", size = 295809, upload-time = "2026-08-10T13:25:34.023Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/60e4bbcc89252037b18087f2ec16405d5b2d5be42dde191bbf3667e96102/greenlet-3.5.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053", size = 611910, upload-time = "2026-08-10T14:14:35.18Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/cd5134be659cd4a443e7a61ae670dabec165a814c51162916d637b6dd38e/greenlet-3.5.5-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95", size = 624198, upload-time = "2026-08-10T14:27:25.229Z" }, + { url = "https://files.pythonhosted.org/packages/9b/30/87c212b5c684d0e72974f1063b7a9687631e8985902c06e1016542c874e7/greenlet-3.5.5-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ca5d6ae0739e5764f2cfcfaa562ac5a990cbdaedca93251c5e3cf07c362371f", size = 629504, upload-time = "2026-08-10T14:30:07.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/ac/5c5b959999b6f09c3026b5dfe171575bc3121c5236ce74f495096f25b203/greenlet-3.5.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:147b25a42e5ca5be3d42356e8f608b37af715a1c196e9bf9d1627f3341adfe1d", size = 621439, upload-time = "2026-08-10T13:40:49.391Z" }, + { url = "https://files.pythonhosted.org/packages/63/2c/eb487fafc9f50ffff2b1e0b697f70fb34bf150821c08ab225aacf5583a7e/greenlet-3.5.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:1b5ed9162c0c098e0bbc2cf88a94f433c1b8926f831745252e099e5d83e17759", size = 432462, upload-time = "2026-08-10T14:30:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6acf112ed8aee499f25b4d6949820fb02ac950ff9c1f3d793bd5be0599f2/greenlet-3.5.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:27493374cff1d1b7919dc8126547f2aea582737e3046147b434b1e12de56389b", size = 1581342, upload-time = "2026-08-10T14:15:05.653Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/734e5f198888876b42d7616ff6644c075baf6b8a2412deadd6b0e1b8b20c/greenlet-3.5.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:12e2ee66c2aba86133f10fd99d6a8856c6d351ffb7be0e4d52ef2cc5fbb705b2", size = 1645744, upload-time = "2026-08-10T13:40:30.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/30/1f42b88dc587b5899ee50616ad56ee40cafaf225df4fb829f10183c62a5c/greenlet-3.5.5-cp312-cp312-win_amd64.whl", hash = "sha256:49ddacd36af37735fab103846f4ee4d18a492dde72730d1699c0c8ebe30d9f18", size = 324171, upload-time = "2026-08-10T13:28:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/76/e5/4dee4d8d2e603fe5fdd7b444e63219f7b9bd852c60c6214511c7157cbe88/greenlet-3.5.5-cp312-cp312-win_arm64.whl", hash = "sha256:5f1b1ff4828cdc1aba4266aff814085d04a1d07959287219af021b838b265d52", size = 308362, upload-time = "2026-08-10T13:26:46.839Z" }, ] [[package]] name = "httplib2" -version = "0.31.0" +version = "0.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/77/6653db69c1f7ecfe5e3f9726fdadc981794656fcd7d98c4209fecfea9993/httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c", size = 250759, upload-time = "2025-09-11T12:16:03.403Z" } +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/8c/a2/0d269db0f6163be503775dc8b6a6fa15820cc9fdc866f6ba608d86b721f2/httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24", size = 91148, upload-time = "2025-09-11T12:16:01.803Z" }, + { 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]] name = "idna" -version = "3.11" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] @@ -413,11 +429,11 @@ wheels = [ [[package]] name = "invoke" -version = "2.2.1" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" }, + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, ] [[package]] @@ -443,11 +459,11 @@ wheels = [ [[package]] name = "jmespath" -version = "1.0.1" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] [[package]] @@ -464,16 +480,16 @@ wheels = [ [[package]] name = "jsonpointer" -version = "3.0.0" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, ] [[package]] name = "jsonschema" -version = "4.25.1" +version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -481,9 +497,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [[package]] @@ -500,24 +516,23 @@ wheels = [ [[package]] name = "keystoneauth1" -version = "5.12.0" +version = "5.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "iso8601" }, { name = "os-service-types" }, - { name = "pbr" }, { name = "requests" }, { name = "stevedore" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/16/b96df223ca7ea4bfa78034b205e0eaf4875bfecb2f119f375fc5232d2061/keystoneauth1-5.12.0.tar.gz", hash = "sha256:dd113c2f3dcb418d9f761c73b8cd43a96ddfa8a612b51c576822381f39ca4ae8", size = 288504, upload-time = "2025-08-21T09:34:10.767Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/627b01cde69d0ece2fd552b8c7c34af06acf13a0a77d1829ff0b46a3b45f/keystoneauth1-5.15.0.tar.gz", hash = "sha256:ce2cacdfd028e65bd23ff403d6572ebfab3b006d6d2dde3aa85c263675a9fbb5", size = 288544, upload-time = "2026-07-03T16:21:31.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8a/803a45dc660770ac7e2d74fc1260a15ade29d2234120854747491b4a7a02/keystoneauth1-5.12.0-py3-none-any.whl", hash = "sha256:2e514b03615e2d9162f0c07c823a61a636e6d4df38ff4a34b7511a04e8a4166a", size = 343402, upload-time = "2025-08-21T09:34:09.38Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/6552fed59449604d982ffe9fe202b82f0724d333319812d1f544a95d3a09/keystoneauth1-5.15.0-py3-none-any.whl", hash = "sha256:9e2a4cf45cc2e2164093157091bc073eda1f334f755e8fab9fd25420c6f6acef", size = 342364, upload-time = "2026-07-03T16:21:29.866Z" }, ] [[package]] name = "keystonemiddleware" -version = "10.12.0" +version = "13.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "keystoneauth1" }, @@ -535,14 +550,14 @@ dependencies = [ { name = "requests" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/20/99aa84ef91b9ed7365562a9fd9a4e31d7cc68201162094a618446c67fc39/keystonemiddleware-10.12.0.tar.gz", hash = "sha256:0da92b4af5178410e15a1b99f56d9cdeb2546eed088c69bc39e666fe09f869bf", size = 215566, upload-time = "2025-08-21T09:35:55.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/9c/26b7ad82227571d1267aac6f03a2625967f5b8a7ddf7a9ebb72b6e595dc0/keystonemiddleware-13.0.0.tar.gz", hash = "sha256:2b52b2f840bee72568666e69f9cf82b9ce1558d6124ce25f0f262083494b4b19", size = 205173, upload-time = "2026-05-13T09:21:00.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/20/410d86b83bfdcb70308d40994958c329afb44587e42a8402d747cb7ced05/keystonemiddleware-10.12.0-py3-none-any.whl", hash = "sha256:459c0c0811e60d13c4fbb7c2065ba83d8bfc288c0f322a301157322ea127f075", size = 152090, upload-time = "2025-08-21T09:35:54.055Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/7f3688da851d0adeec6b78f6a49ba1a6073727f220a64839d2dffba60122/keystonemiddleware-13.0.0-py3-none-any.whl", hash = "sha256:d2102b1dbac26f985a6bfdb003e0dc84fb193106ee1c99bd7470af1d1269ec94", size = 135376, upload-time = "2026-05-13T09:20:59.296Z" }, ] [[package]] name = "kombu" -version = "5.6.1" +version = "5.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "amqp" }, @@ -550,59 +565,59 @@ dependencies = [ { name = "tzdata" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/05/749ada8e51718445d915af13f1d18bc4333848e8faa0cb234028a3328ec8/kombu-5.6.1.tar.gz", hash = "sha256:90f1febb57ad4f53ca327a87598191b2520e0c793c75ea3b88d98e3b111282e4", size = 471548, upload-time = "2025-11-25T11:07:33.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/a5/607e533ed6c83ae1a696969b8e1c137dfebd5759a2e9682e26ff1b97740b/kombu-5.6.2.tar.gz", hash = "sha256:8060497058066c6f5aed7c26d7cd0d3b574990b09de842a8c5aaed0b92cc5a55", size = 472594, upload-time = "2025-12-29T20:30:07.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409", size = 214141, upload-time = "2025-11-25T11:07:31.54Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0f/834427d8c03ff1d7e867d3db3d176470c64871753252b21b4f4897d1fa45/kombu-5.6.2-py3-none-any.whl", hash = "sha256:efcfc559da324d41d61ca311b0c64965ea35b4c55cc04ee36e55386145dace93", size = 214219, upload-time = "2025-12-29T20:30:05.74Z" }, ] [[package]] name = "lxml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/a9/970b8fa0ecc4fbf1dfaed0d89bbc1fc1421b25ec26a2038c91e872dc6c8e/lxml-6.1.2.tar.gz", hash = "sha256:1055241852f2b02068af4a625a5d32c087db193c12251928af2562ecd2239f18", size = 4210626, upload-time = "2026-08-19T04:58:15.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/a4/55eb54507073089ab27743c5da2113c84f0d0b1715b33175fdd943c9652d/lxml-6.1.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d506bdba580ecb1a6ad2e2b5c49445e66d3e1f95894885739094393a1aad237", size = 8602111, upload-time = "2026-08-19T04:58:28.017Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bf/6332f45d78da385bb01d5cac3fe4acda19f025d1307cbc7ad538355fecbb/lxml-6.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12acd337d2821cb8b9247dfe4b7aa2f2769a3df5ae8511b7e550df42b8f4d3c3", size = 4638376, upload-time = "2026-08-19T04:58:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/68/e0/21fba0fe74d417fbe976903ae6bc77e92cdce01aae7b636abd87756f4588/lxml-6.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5078ff51e6316c0f75ea8127c2cd24374747fb351f62fb93d1761f8ae5a04a40", size = 4939689, upload-time = "2026-08-19T04:58:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/ce3e885264fdd0bdcb6b49c1ea1842f94281b39e4ff956099e8d57532c60/lxml-6.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9477e14217c212e6023c994a71a1a349db19b0e10fd5bf189666b281ae63b1fd", size = 5105185, upload-time = "2026-08-19T04:59:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b6/990a8446c488c70fa25681e150de94b7bf2eaaf387e374d195ab3c8faafb/lxml-6.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261d98065326676d7253882db0198d0aa06748d7ee0443367acf10b148273f99", size = 5011863, upload-time = "2026-08-19T04:59:50.58Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6a/f70f41363dae27e3bfd6224b128f5ba150874bd32ca4938552930ffa33b0/lxml-6.1.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0666943ee1576fa890a6dc6316ef42e8241b5dd56f67bc5475acb2ac298c6ca9", size = 5638234, upload-time = "2026-08-19T05:00:00.802Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/a65b64f34d556925faef2c4f14167d58c571bc15a3e1f2bba71138830562/lxml-6.1.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04cf9e3f4ee9cab9d9ba05401bef8668840fa9620fcd4d8e85a2d2fd0b0fa960", size = 5244532, upload-time = "2026-08-19T05:00:07.516Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a9/471552e015e954fc9d960aa27c3d67ebf489683d03f033399a790417c67c/lxml-6.1.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:9429d2371d406344ed1da5b5686d9412e74137c07b0171278368ff704f470ed5", size = 5358194, upload-time = "2026-08-19T05:00:22.747Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0f/bc6248fbec2cc416f102b1267f1567e07510f6fa909bbe8cd2a22d6fb78e/lxml-6.1.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:eff128ffdc093cc6317955934ad9751105d37ed8dbca3ff4ccd751af6be37185", size = 4704432, upload-time = "2026-08-19T05:00:51.115Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3f/cec859f50e63f1fa338fab43d2362d7543e1237f2475960d8ab0769de0eb/lxml-6.1.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ba58574d710b82ead7cbedea01cac3e110bc3ef82d4731519b74a2c11f7cf5e9", size = 5255038, upload-time = "2026-08-19T05:00:58.895Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d9/2ced0cf2967115f92a1b8b3ae6bd18763abc3ebef88c98cf25145fda396c/lxml-6.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52f6d4dff133c9778a24e9a2cfc1608930b15869866171aacc5131b5a418a003", size = 5054481, upload-time = "2026-08-19T05:01:10.096Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f5/4f07386d3c88673daeec3b8cc09a2a4d39fa01c1fc49009791b0746d97fa/lxml-6.1.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8807998c1023d1e9d60e02500f90e85a0752dbc0b670989806bba87b82dd5b42", size = 4785535, upload-time = "2026-08-19T05:01:18.909Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5a/f4fe3ecbc189f48fba2547c5db5c940a10151d3e86b856a60a533a77e816/lxml-6.1.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2170d0a280c877b6e2dc6738217db947be35dd8cf09ca458b355aa1bab2a9e70", size = 5655337, upload-time = "2026-08-19T05:01:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/f586aa1bf27bfbace2dfdbb704da5c52f0bdece8ee440c8fb4946c940b2e/lxml-6.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c67f3c1278f942e97d8665c2a690324aaea5137de16f056583a21f0ac706177f", size = 5245778, upload-time = "2026-08-19T05:01:45.227Z" }, + { url = "https://files.pythonhosted.org/packages/18/a1/677494bbaef4d6db5e4633af817414f478865850b55c03ae4bf70fa7b8ca/lxml-6.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093fbf547d0f3ca02705381f795a050fbb58988be4aac7f79f99f280c4082313", size = 5267274, upload-time = "2026-08-19T05:01:57.687Z" }, + { url = "https://files.pythonhosted.org/packages/5a/71/b71425b8764d4cb7c92eb970483be7d5610dce2a6316242b5aaae7d260be/lxml-6.1.2-cp312-cp312-win32.whl", hash = "sha256:be365ce8d2d411cf2fb573747684b4fd470fa6224e0094d9d5a21155acc369d3", size = 3602563, upload-time = "2026-08-19T05:02:01.837Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fb/909584e16d2148c1a252cc2c32dd99fe0e2682459c586d3d7a192e74a0ae/lxml-6.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:b97153ca609b434b712ddfb92cd6af101a7045a7724c542258bd4727a344472f", size = 4005965, upload-time = "2026-08-19T05:02:07.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/41207c9212caad0b52749e34739fb9bfab67486729f52a8fe9bd9266fee6/lxml-6.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:7feb72424f19a893ae4f3373c7aae821b1aacb6076b708915c651f0683a97c49", size = 3666641, upload-time = "2026-08-19T05:02:11.3Z" }, ] [[package]] name = "mako" -version = "1.3.10" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/12/b5fa2353e2754cd67fb9f83793fa48ff42c213a5da7e719869d2301f6ab8/mako-1.4.1.tar.gz", hash = "sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27", size = 410165, upload-time = "2026-08-05T06:10:56.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, + { url = "https://files.pythonhosted.org/packages/a5/54/12ed58d458474aaab5c3d180173e745a4fe131bb330370596876d19ff60f/mako-1.4.1-py3-none-any.whl", hash = "sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617", size = 80010, upload-time = "2026-08-05T06:10:58.248Z" }, ] [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[package]] @@ -635,30 +650,35 @@ wheels = [ [[package]] name = "msgpack" -version = "1.1.2" +version = "1.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +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/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, - { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, + { 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" }, ] [[package]] name = "ncclient" -version = "0.7.0" +version = "0.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml" }, { name = "paramiko" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/fb/29dbb4987f81d7932d8018079bf3e6d1d5cce320d7c2c90e84d3c7dba40c/ncclient-0.7.0.tar.gz", hash = "sha256:318e8e3e72b1d2a766f3665cabef33436fd25b607da5f15657a199c648a68435", size = 116221, upload-time = "2025-08-25T15:29:24.172Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/92/e36995ee7ce151814335583571bed95f1a5a491b7ea4d5487cb67377800a/ncclient-0.7.1.tar.gz", hash = "sha256:60dabb6ac1a2d84fbc3349cb104ef7d0c5d12cf8eee43fefc715410d70410ddc", size = 123086, upload-time = "2026-03-15T16:05:49.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/7c/c0c06d1696b03b901fa5a854b03e16f35c5c5cc8f00a0f43217dc467cd39/ncclient-0.7.1-py3-none-any.whl", hash = "sha256:47beeeee6074bd70a9215c4d353b51c7237af3c5c15269d81692810f2aa15147", size = 94833, upload-time = "2026-03-15T16:05:47.879Z" }, +] [[package]] name = "netaddr" @@ -671,8 +691,8 @@ wheels = [ [[package]] name = "neutron" -version = "28.0.0" -source = { registry = "https://pypi.org/simple" } +version = "28.0.0.0b3.dev316" +source = { git = "https://github.com/rackerlabs/neutron.git?rev=b0678e56e9c5a7ec2f335c4271465fc1c3202e2a#b0678e56e9c5a7ec2f335c4271465fc1c3202e2a" } dependencies = [ { name = "alembic" }, { name = "debtcollector" }, @@ -715,6 +735,7 @@ dependencies = [ { name = "pecan" }, { name = "psutil" }, { name = "pyopenssl" }, + { name = "pyroute2" }, { name = "python-designateclient" }, { name = "python-neutronclient" }, { name = "python-novaclient" }, @@ -726,15 +747,11 @@ dependencies = [ { name = "tooz" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/df/071c7f72f683f83fd30f1d6d20071efafe95f2fc0ce2a9de2fb9011c4706/neutron-28.0.0.tar.gz", hash = "sha256:c99063615e66f55d02b2a7700385bb89851204942d5150058d18fa01a6b31776", size = 12314767, upload-time = "2026-04-01T10:57:14.322Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9a/1b19053a48bef10fe1b9ae8c1b3296c5c08d73834dabab3df312b8544778/neutron-28.0.0-py3-none-any.whl", hash = "sha256:efd6d38497e74e3f532253595c478aef49225aee1fba1615075337314bf1b4e2", size = 4437292, upload-time = "2026-04-01T10:57:11.78Z" }, -] [[package]] name = "neutron-lib" -version = "3.25.0" -source = { registry = "https://pypi.org/simple" } +version = "3.24.1.dev8" +source = { git = "https://github.com/rackerlabs/neutron-lib.git?rev=6d0367183cc92656ba449acefd830cb4e11f2f5b#6d0367183cc92656ba449acefd830cb4e11f2f5b" } dependencies = [ { name = "debtcollector" }, { name = "keystoneauth1" }, @@ -761,10 +778,6 @@ dependencies = [ { name = "stevedore" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/35/3e9162c20a0de0edb8965adfb033118229e2970885bc5d0fbab324989586/neutron_lib-3.25.0.tar.gz", hash = "sha256:2d160440f4e572939ca2f157b1546536f3ff0ffb20514c0d3f7119e216fd6172", size = 561912, upload-time = "2026-04-20T12:23:28.971Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/17/b9dea87fda8d984b329d6a48423683f88a6035a38e79fa053af3ae080a3c/neutron_lib-3.25.0-py3-none-any.whl", hash = "sha256:619a2886a06e661a3225b94e875ddd8a8ef064614d45e5958eb1c21b105ea1c5", size = 639833, upload-time = "2026-04-20T12:23:27.225Z" }, -] [[package]] name = "neutron-understack" @@ -782,17 +795,23 @@ dependencies = [ [package.dev-dependencies] test = [ + { name = "fixtures" }, + { name = "oslotest" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-mock" }, { name = "sqlalchemy" }, + { name = "testresources" }, + { name = "testscenarios" }, + { name = "testtools" }, + { name = "webtest" }, ] [package.metadata] requires-dist = [ { name = "keystoneauth1", specifier = ">=3.14.0" }, - { name = "neutron", specifier = ">=27,<29" }, - { name = "neutron-lib", specifier = ">=3,<5" }, + { name = "neutron", git = "https://github.com/rackerlabs/neutron.git?rev=b0678e56e9c5a7ec2f335c4271465fc1c3202e2a" }, + { name = "neutron-lib", git = "https://github.com/rackerlabs/neutron-lib.git?rev=6d0367183cc92656ba449acefd830cb4e11f2f5b" }, { name = "openstacksdk", specifier = ">=4.9.0" }, { name = "oslo-config", specifier = ">=9.7.1" }, { name = "oslo-log", specifier = ">=7.1.0" }, @@ -801,15 +820,21 @@ requires-dist = [ [package.metadata.requires-dev] test = [ + { name = "fixtures" }, + { name = "oslotest" }, { name = "pytest", specifier = ">=9.0.1,<10" }, { name = "pytest-cov", specifier = ">=7,<8" }, { name = "pytest-mock", specifier = ">=3.14.0,<4" }, { name = "sqlalchemy" }, + { name = "testresources" }, + { name = "testscenarios" }, + { name = "testtools" }, + { name = "webtest" }, ] [[package]] name = "openstacksdk" -version = "4.16.0" +version = "4.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -825,27 +850,14 @@ dependencies = [ { name = "psutil" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/43/49b126e9ccfa19647d2f0aff26321caded607522d29c3d9495d44f4b9471/openstacksdk-4.16.0.tar.gz", hash = "sha256:466640c6d2b813b782d4ad58a4f2960633e8e58f147ec0baa2019454de190d30", size = 1397114, upload-time = "2026-06-17T08:43:22.774Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/ea/60a4a274c4991847a085ac4a36b19d09f44f04a22adcfda6ca631763322b/openstacksdk-4.16.0-py3-none-any.whl", hash = "sha256:0469939bd7fc1b80979e4440dd8f8026eea302bdd979fac5437b56ada3780a62", size = 1982817, upload-time = "2026-06-17T08:43:21.148Z" }, -] - -[[package]] -name = "os-client-config" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "openstacksdk" }, - { name = "pbr" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/cd/352f6f18d1fb90780b95fdc3a668a279bd41d89905d70ee06076b529077c/os_client_config-2.3.0.tar.gz", hash = "sha256:e16a260f2fd500af14f157b9b7b7d69292ce83b0f8a461ec68ce6a8a42967cbd", size = 49928, upload-time = "2025-07-08T09:00:46.701Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/d0/514c38d0b7f4d3652321baf0c5136ac29ce9360a2094fcdcd78f865cb7c9/openstacksdk-4.18.0.tar.gz", hash = "sha256:466f2f869bcf6dec717a5e6c65c0522b1bd061d53310c7bf11982e0d9244f70c", size = 1403478, upload-time = "2026-08-03T12:27:08.152Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/d1/0f6fe5650516fd5113ca64f3efbdd35c91be14ead96065b835207a9cd75a/os_client_config-2.3.0-py3-none-any.whl", hash = "sha256:bac8486bbafc0dd92704c29d5581d65030912804e8e675d13bc43b76ce051c82", size = 30898, upload-time = "2025-07-08T09:00:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/1c68b79bc71b1d9ea1d7acfd9a90f52bddfc2485ac3f8e1d1be488fcbcb8/openstacksdk-4.18.0-py3-none-any.whl", hash = "sha256:17d033c19a07df90116b67c0750b2218acdc45cdcf659d4c137f084667b26993", size = 1993600, upload-time = "2026-08-03T12:27:06.589Z" }, ] [[package]] name = "os-ken" -version = "4.2.0" +version = "4.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "eventlet" }, @@ -859,9 +871,9 @@ dependencies = [ { name = "routes" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/58/a3faf90cd877695018cf03d4a0ea935c050afa293ecb24cebd440335563e/os_ken-4.2.0.tar.gz", hash = "sha256:c7120748e93bca060427e3e5624a110a5c5b7bb907f8bb3ba4c1f77a07c5cb19", size = 1399545, upload-time = "2026-05-08T11:39:31.143Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/81/3dc912cca417f80d5b0871904c6e1b7b7002de867b5a7981f2a8d1d45c43/os_ken-4.2.2.tar.gz", hash = "sha256:bf6a640c0e67c377d4e6c0014f1de36bbf53d16eb7ecb080e5f9b5ede9bbf836", size = 1402804, upload-time = "2026-08-20T15:13:06.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/1e/577ef857ddefa74d503d525922fe7b086c09f1e324fab972aa2d5d9e2a95/os_ken-4.2.0-py3-none-any.whl", hash = "sha256:eb688d599f193258940ab50a55a59c4ab1243bc126bcef6d04d8bfaab04bdeec", size = 2022300, upload-time = "2026-05-08T11:39:29.451Z" }, + { url = "https://files.pythonhosted.org/packages/7b/02/9758ad2b9b27e2a4ba616ab66d5d5dcc31d18780c0afaac37997b7b6ce03/os_ken-4.2.2-py3-none-any.whl", hash = "sha256:d61b016b5e753a64f3652014e080ce9e216540a620666fc22835f880dbf63d3b", size = 2024694, upload-time = "2026-08-20T15:13:04.443Z" }, ] [[package]] @@ -878,32 +890,32 @@ wheels = [ [[package]] name = "os-service-types" -version = "1.8.2" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pbr" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/62/31e39aa8f2ac5bff0b061ce053f0610c9fe659e12aeca20bfb26d1665024/os_service_types-1.8.2.tar.gz", hash = "sha256:ab7648d7232849943196e1bb00a30e2e25e600fa3b57bb241d15b7f521b5b575", size = 27476, upload-time = "2025-11-21T13:55:47.726Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/ae/fe7ac23155ae0b4b9779e06e9c5bb4070f2315dc4ca886a88fa3230d344b/os_service_types-1.9.0.tar.gz", hash = "sha256:1f2e5fb71d1f6f4ff31d8992674f2368465bc2f25cd94018015c3ddbfc5c617f", size = 28087, upload-time = "2026-08-04T13:07:43.171Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/26/0937af7b4383f1eba5bca789b8d191c0e09e59bb64962b18f4a14534ce41/os_service_types-1.8.2-py3-none-any.whl", hash = "sha256:f78890d71814deffabf0ed4358288ec2ced579bc4d0bb87a79ae806cbb4deb6e", size = 24876, upload-time = "2025-11-21T13:55:46.093Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/f8ae99ae9bb54cd63e7ab89251b2a4ff4ff07f02847403948b6a6f50f816/os_service_types-1.9.0-py3-none-any.whl", hash = "sha256:f268545c896434177bf55c43f7d5129e65210d5d456df3f00a1c9832a71c3a4b", size = 25282, upload-time = "2026-08-04T13:07:42.258Z" }, ] [[package]] name = "os-traits" -version = "3.5.0" +version = "3.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pbr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/05/a483b40340af7d65f9c76424521f198ad4229032641905b7d5bdcc96a478/os_traits-3.5.0.tar.gz", hash = "sha256:65f0944bc885c1ff4454ab8e74844194863df8b05cf78c7c8c803ef295c8761d", size = 32513, upload-time = "2025-05-15T05:24:22.621Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/54/68070c55d96437e5189e46f81781b8e5e7113f9bd0505480bc9c023bb7e1/os_traits-3.9.0.tar.gz", hash = "sha256:4c68ea8de3dc8088f4f87f382558291a0b1c679e63be796332611539fc11ff03", size = 33656, upload-time = "2026-08-21T19:27:30.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/02/1cc710a536994ba27263a33dea7befb638f16f9e5fce577fd46897681b00/os_traits-3.5.0-py3-none-any.whl", hash = "sha256:238cb8de19c383a61c7682c90ddc202311f3bde4f92687325f2cc26dac174973", size = 43668, upload-time = "2025-05-15T05:24:21.133Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/67908de48b0c5f204b00b73e83a3217d50fa9ac7d78279861634ca39f976/os_traits-3.9.0-py3-none-any.whl", hash = "sha256:442a30cdd4187f04758956a3432e51b89ba0cb07049d96c39ec0dd34387da81a", size = 44744, upload-time = "2026-08-21T19:27:28.89Z" }, ] [[package]] name = "os-vif" -version = "4.2.1" +version = "5.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -917,17 +929,16 @@ dependencies = [ { name = "oslo-versionedobjects" }, { name = "ovsdbapp" }, { name = "pbr" }, - { name = "pyroute2", marker = "sys_platform != 'win32'" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/8a/3f0e417a1456583a3b7e1a2974a7cfe24b2a2539c96045e6cbbd5624421a/os_vif-4.2.1.tar.gz", hash = "sha256:372ddfd471a955ecb813543829be91869fd51c51d3a1f6866a96b56954de4c52", size = 104317, upload-time = "2025-08-25T13:03:28.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/35/3eb7a6825f8dbcdb50f7dd2c093f6e2f4900e564faf854c008a1d96a54c9/os_vif-5.2.0.tar.gz", hash = "sha256:7fa8e32a05b3cd1a12369b93c90968c4de290106af826d1d67bb7209e7a940c9", size = 104043, upload-time = "2026-08-21T19:40:30.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/09/e48d406ecf4eb73bda077c6ed2f8e92d2b5923ecf55e3b2213ab4f3b403c/os_vif-4.2.1-py3-none-any.whl", hash = "sha256:7c9b47285b7dcb715956053a09d46b0a101bf3304fd0a59e99ecbaaafa7963ac", size = 110971, upload-time = "2025-08-25T13:03:27.545Z" }, + { url = "https://files.pythonhosted.org/packages/c8/dd/ff8039231559894d8d3603a44f47fc4824cf6eb5b9a049d3e5abcf6f94fd/os_vif-5.2.0-py3-none-any.whl", hash = "sha256:8fa38309a2ba23db384f095eb30872cceee18bc7c5d88f5e75efe4fe94968d76", size = 104556, upload-time = "2026-08-21T19:40:29.461Z" }, ] [[package]] name = "osc-lib" -version = "4.2.0" +version = "4.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cliff" }, @@ -939,14 +950,14 @@ dependencies = [ { name = "requests" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/63/ea0eda39e1b2c0ca3e9d87798568f881ea7aa90e4f94affb78676215f72d/osc_lib-4.2.0.tar.gz", hash = "sha256:99718f06a990c1ad6fb9034bbed9655390a2ea83cef71a53781e7e9abd9f20ce", size = 101802, upload-time = "2025-08-21T09:35:30.372Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/80/37ac2a46cc3ea9348ea670fd76dad72888726191f5875278a3d85034a9d8/osc_lib-4.7.0.tar.gz", hash = "sha256:5b896de12ed69fb1111d2971467d403b838d414fc27df2024fc50e8652a53f2b", size = 105896, upload-time = "2026-07-10T12:56:56.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/9a/5943674892bda78e4671a553e68406d6e7f2ff278d253d6c991b1fe040cf/osc_lib-4.2.0-py3-none-any.whl", hash = "sha256:951d25c0975765242816c9e8f86a8e223dfae78a23ed8eb5a8e1c9c8dd688d58", size = 92898, upload-time = "2025-08-21T09:35:29.053Z" }, + { url = "https://files.pythonhosted.org/packages/2b/3c/adce48d55f5688d5c46d716162ab9a38794f09bc0c87a3cf32383cfa42f1/osc_lib-4.7.0-py3-none-any.whl", hash = "sha256:a6b14d8a1d9a487334925d377b2b249d5f7317eb57e53e89de3a2627fe268d6d", size = 97314, upload-time = "2026-07-10T12:56:55.13Z" }, ] [[package]] name = "oslo-cache" -version = "4.0.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dogpile-cache" }, @@ -955,14 +966,14 @@ dependencies = [ { name = "oslo-log" }, { name = "oslo-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/ee/82c4545847fc0c738028a49cbe57b0f00ddae17e6a1728b5a509a27c0cef/oslo_cache-4.0.0.tar.gz", hash = "sha256:b4568ea8d12a291bb3ea2346d74bfbd50ca8f86b5394014ba31e90d9e6f03b79", size = 70605, upload-time = "2025-11-19T19:32:12.563Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/19/e89474be0b62e2f61c089ad9c437ddf8fb1fc482c61ca7e7e29e19f0c086/oslo_cache-4.3.0.tar.gz", hash = "sha256:c747ada25e20925746da36a208eec50f0d4c310fcaac10b436b69393ee8140fe", size = 71016, upload-time = "2026-07-10T13:53:32.579Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/ee/c15127ab0c4fe68d53b903e2248a56939c555870c435d714ced653e77394/oslo_cache-4.0.0-py3-none-any.whl", hash = "sha256:b1cec8f48e230d94dba30d0efffe7482062f321b983676612110f2f0ca182a1e", size = 65919, upload-time = "2025-11-19T19:32:11.246Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e2/c694e9f9300e796d8a0f2c62bf3ea657de2dbb90d87a1bfda1b2a028beff/oslo_cache-4.3.0-py3-none-any.whl", hash = "sha256:16ceeb61e07eb5bc010ad29d3a273ab0806c4a4a61c46ea8a1b0320a7739e1a1", size = 65296, upload-time = "2026-07-10T13:53:31.587Z" }, ] [[package]] name = "oslo-concurrency" -version = "7.2.0" +version = "7.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -972,14 +983,14 @@ dependencies = [ { name = "oslo-utils" }, { name = "pbr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/bd/727bf8de6ef870b400702cd57826e8e298f2d906ab2f8dcc3890c421711f/oslo_concurrency-7.2.0.tar.gz", hash = "sha256:160e814c8549f6a464e77a04265ed996d581bdd3fd3bd4ebf311d3446bb7e1aa", size = 60285, upload-time = "2025-08-25T12:53:05.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/d8/cd9a472611c8ce11c858195885c3e9383c054cca52e1f66ce287f6988d00/oslo_concurrency-7.6.1.tar.gz", hash = "sha256:8dcd3a6e931fc24d53832e887a124e3872e44e1af015cab47783bc67ad5b85e7", size = 64052, upload-time = "2026-07-16T08:58:12.581Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/ab/7afe0703bc50081d6e82b00dce95285735926245b21b323018410cc84e72/oslo_concurrency-7.2.0-py3-none-any.whl", hash = "sha256:9bf8953572ba8755aaec6f0bcca6ee2a5e9128bef1827da5f49caa78caf71fae", size = 47066, upload-time = "2025-08-25T12:53:04.006Z" }, + { url = "https://files.pythonhosted.org/packages/f0/fd/c3d55e23b7de17704563364b40613b63a244fa9535bb5dc41f67067d8d54/oslo_concurrency-7.6.1-py3-none-any.whl", hash = "sha256:37cc117c5d0600542d3d88ce03efa96f53fc9324cdf3c056695674a680c488b2", size = 49130, upload-time = "2026-07-16T08:58:10.843Z" }, ] [[package]] name = "oslo-config" -version = "10.4.0" +version = "10.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "netaddr" }, @@ -989,27 +1000,27 @@ dependencies = [ { name = "rfc3986" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/a9/a1295eceb3a79ad46f32d145bade3119dc20636e2fda62adaba19c61195c/oslo_config-10.4.0.tar.gz", hash = "sha256:2ae3e02593474ecd7b64ec4eb11482adb4c928a78267bc820f5c3f80240b197a", size = 168943, upload-time = "2026-05-18T09:31:19.554Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/e9e312ca216eaa9e0fa76f8d8fb9aeb14ea56f136136ec6753a11df24237/oslo_config-10.7.0.tar.gz", hash = "sha256:4f0fd4ca7ecfe511fbc0e012d3c63fce57009f360199e9e9cc2329ed55ec911e", size = 177161, upload-time = "2026-08-07T11:14:20.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/49/87743b93ea01f9cf667e78c1ceea1058f816ef733399f4e45469472cf2fe/oslo_config-10.4.0-py3-none-any.whl", hash = "sha256:0429c7b312114fb796005bdb718bba51e66de9c0814fb2cee22e754afbfe253b", size = 137504, upload-time = "2026-05-18T09:31:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/55/12/bec8c6f23dc1e0eb1adffe28a4450ac24c3b1b7ae89a09be4e1764492dcc/oslo_config-10.7.0-py3-none-any.whl", hash = "sha256:d4e80b535fb208cb15cf0ce6f6fd976eebd694f21dc37e3f3d68a360cbe7eab5", size = 143839, upload-time = "2026-08-07T11:14:19.729Z" }, ] [[package]] name = "oslo-context" -version = "6.2.0" +version = "6.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pbr" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/56/07d71967a0b0ed3e4409df64b9aaa023ffc2cf3452e042bcd6559e329da9/oslo_context-6.2.0.tar.gz", hash = "sha256:465c67da4d92dc2960deec148dee469d7ae7736af869581e2e3b73ce2cfa27a8", size = 34983, upload-time = "2025-11-20T15:18:32.437Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/6b/71f00290f6fb7302178422d3478093aacf972ab3e6e0f4b9a91026f533f8/oslo_context-6.5.0.tar.gz", hash = "sha256:7e1fb03c6a97167959f37d930300154e0ee837ecdb85798c2bbe8878b56caaaf", size = 35642, upload-time = "2026-07-10T13:48:26.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/dd/a76cb78fd6d08eb07b613fb4dc68259beeaa922d9907371542462d8bdde6/oslo_context-6.2.0-py3-none-any.whl", hash = "sha256:6ea0b5d04c3577090d6513ce4533a74cdcee958e34a6fc54d820267b799f9d29", size = 19853, upload-time = "2025-11-20T15:18:31.356Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ab/be9bc57dbc54f07083a025736f6ba835ce2d89278200dbc9293010cade63/oslo_context-6.5.0-py3-none-any.whl", hash = "sha256:6f3f038917711c03fa34bae1867076df24bd7a5ab5d3bf0bd0407869c5274d7e", size = 20097, upload-time = "2026-07-10T13:48:25.238Z" }, ] [[package]] name = "oslo-db" -version = "17.4.0" +version = "18.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -1017,31 +1028,31 @@ dependencies = [ { name = "oslo-config" }, { name = "oslo-i18n" }, { name = "oslo-utils" }, - { name = "sqlalchemy" }, + { name = "sqlalchemy", extra = ["asyncio"] }, { name = "stevedore" }, { name = "testresources" }, { name = "testscenarios" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/99/9bbfc917740b79bc4a96a07938cff8792c47963a63fa9a8a36f216a6ced6/oslo_db-17.4.0.tar.gz", hash = "sha256:14b62f58c416330cbb188a5329b14d95017661ef8aea1d72a0ff924eecf910a9", size = 172000, upload-time = "2025-08-07T13:58:18.372Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/dd/3ed5c23e1f92882664fc65c55cd6b3756b4679f93289760d567df0685561/oslo_db-18.1.1.tar.gz", hash = "sha256:5239ba059b94c381c61b5abe607adcd15eaee8df115deaddb8a36dcb366e44fa", size = 168774, upload-time = "2026-08-24T13:58:03.741Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/b5/81aee8626a22b3e656ea55a2d7adaa9eafe156dc42159eda96ef246ecdd6/oslo_db-17.4.0-py3-none-any.whl", hash = "sha256:c63cf2f91e3403a29f068ea10d7ed1769db3664c349a805291fb3a069cddca4d", size = 157434, upload-time = "2025-08-07T13:58:16.581Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e7/3d4aacb5ce83f0173d29ca60bd8e93b63cff4328974f4a1f278f019adb40/oslo_db-18.1.1-py3-none-any.whl", hash = "sha256:d8ba86515e454404a98a6755bc88da116b40b12927840b46fd7b25fbe8d453f9", size = 152466, upload-time = "2026-08-24T13:58:02.547Z" }, ] [[package]] name = "oslo-i18n" -version = "6.7.1" +version = "6.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pbr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/58/9d4a11a838f174180606f71a8eeeb0a443e98e28798d87c150be732c0d32/oslo_i18n-6.7.1.tar.gz", hash = "sha256:7dc879089056fe287a6fb46fa2e73ad88f8d4b989bd63f00486f494435b24ced", size = 49721, upload-time = "2025-11-20T15:14:32.85Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/69/72b03bb4d33f51a157c02d5297227bae48b9c359103856942b8774b608df/oslo_i18n-6.9.0.tar.gz", hash = "sha256:574bcf21873b185068bcec951de1ec093158ffdff05a8055fd18ddcb69f69e65", size = 50369, upload-time = "2026-07-10T13:44:34.301Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/12/6215fd01b902973a8e27c8fc8d1c215fabc12ee0211ae4fe60ae0d0f79dd/oslo_i18n-6.7.1-py3-none-any.whl", hash = "sha256:36f5f88e05415f6bef28c3d7ee0eca634a6b3ada2d38df2c0f8d675dc1bfd5aa", size = 47671, upload-time = "2025-11-20T15:14:31.858Z" }, + { url = "https://files.pythonhosted.org/packages/fa/3a/f9f9be0fd3b5c180b64f08daef70ca20fbbdccab47d0a21b65f0e4b5b8d1/oslo_i18n-6.9.0-py3-none-any.whl", hash = "sha256:354b6cd3ba1acf1f04231f690028fd5b58bd8b7cda7bd9859126bb97152c717b", size = 47847, upload-time = "2026-07-10T13:44:33.205Z" }, ] [[package]] name = "oslo-log" -version = "7.2.1" +version = "8.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -1053,14 +1064,14 @@ dependencies = [ { name = "pbr" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/6b/a7f1c1daeadd36f71633fb3ebac9817fcb1f8edfd06d6bdd71384f39010f/oslo_log-7.2.1.tar.gz", hash = "sha256:01aebabdcf06b62df00e479db99df0c23f6cd24c6500ab3110e604bd059fa8d5", size = 98147, upload-time = "2025-08-25T12:56:15.605Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/c5/f7ac6a80bd298630574fc77bd33603960781b9502115992536e9c1be1ec5/oslo_log-8.3.0.tar.gz", hash = "sha256:8cdd7f082ab51e29453b9cc9fe6faf7eada6ffc2173e4df5d4424ba94191d2a5", size = 101332, upload-time = "2026-07-10T13:10:06.858Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/ee/6b92193f6deb3183caa8386e23489f6199c0d5251cbfc5360c3308d5092e/oslo_log-7.2.1-py3-none-any.whl", hash = "sha256:77a2d8dd2a06074484e9e33fa1d0e31dd4d5dd1e81b34efe2b45762e4db3c996", size = 75065, upload-time = "2025-08-25T12:56:14.379Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/eb498708694690f50ed9ead614839489dc179b68b5b220493bfa53d5bde7/oslo_log-8.3.0-py3-none-any.whl", hash = "sha256:de5bf314181fea25957d398484bf878ab164353568bc890243e64998af4b9cdd", size = 77269, upload-time = "2026-07-10T13:10:05.708Z" }, ] [[package]] name = "oslo-messaging" -version = "17.1.0" +version = "18.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "amqp" }, @@ -1081,14 +1092,14 @@ dependencies = [ { name = "stevedore" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/42/b3ad17c818c2c83ee96055f8f3923bbc66df998b94c8582e225eebe6932e/oslo_messaging-17.1.0.tar.gz", hash = "sha256:84c7e0ebafed29a301f6a2c9ca61e9175923df6af08ee5ed36a419f7070a8bdc", size = 230478, upload-time = "2025-07-11T14:24:02.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/33/eda37a341dad350cc0790180ddce4666287974a842bef07defc9b19d9074/oslo_messaging-18.3.0.tar.gz", hash = "sha256:a468bee8226e3e79878cab1904a8959fd4f924f3a6474470343f51b0818cb745", size = 234370, upload-time = "2026-08-24T14:01:19.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/df/50e2dc3245b36e840d525a72243856ef358a878f8a3c5995246472cb6bb6/oslo_messaging-17.1.0-py3-none-any.whl", hash = "sha256:f6ee06859e2969aa1bdbc85bbfe9c332db932a239b6e27f150d19eafee04c500", size = 202463, upload-time = "2025-07-11T14:24:00.427Z" }, + { url = "https://files.pythonhosted.org/packages/e9/c1/a367ac077352a861cc3440a0f986586cf3f8e6509f720fd74c1142974f32/oslo_messaging-18.3.0-py3-none-any.whl", hash = "sha256:1d83784ddd2a209a6fd1450a4e4dd57b0b86d3522b7a726309b7345944827957", size = 204484, upload-time = "2026-08-24T14:01:18.386Z" }, ] [[package]] name = "oslo-metrics" -version = "0.14.0" +version = "0.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "oslo-config" }, @@ -1097,14 +1108,14 @@ dependencies = [ { name = "pbr" }, { name = "prometheus-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/3e/a2109f559449007e49d0e42598c9fae1d664c39e1e5c340a04c1de17fece/oslo_metrics-0.14.0.tar.gz", hash = "sha256:7d05b7480ae866517753ab4ca5e8819db403dcad7632bf892a42f4b49b9ceaff", size = 21639, upload-time = "2025-11-18T09:43:59.629Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/ee/e09d6eb519c497217a17a99b4f5ff131d5360527eb6e600fabd1b2ad5674/oslo_metrics-0.17.0.tar.gz", hash = "sha256:3fa3d6d6209efdbdcf88840a2016efa53fa6c1a3e98847e60ded0d91d074216b", size = 22966, upload-time = "2026-08-24T14:06:06.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/30/d95958e9e116e76d280e351eb88ddd738c760f5e6ac2be4bc778653ff067/oslo_metrics-0.14.0-py3-none-any.whl", hash = "sha256:a7cfae800ef9de350c2c17728ba8435539d7e0266b52b382a3b052776cbb1b8f", size = 14105, upload-time = "2025-11-18T09:43:57.688Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0d/fe3e62eb7eb0c890d6672740fbc7e188b7e42f3e80e8adf3d3a3524012c7/oslo_metrics-0.17.0-py3-none-any.whl", hash = "sha256:484a06c4767e39a5897919fd1ee21a805e5e350c24564cc50b1c12d00d3f2d30", size = 14623, upload-time = "2026-08-24T14:06:05.684Z" }, ] [[package]] name = "oslo-middleware" -version = "7.0.0" +version = "8.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bcrypt" }, @@ -1119,14 +1130,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/83/55eaed35158c44bfe2384f435aa1d5ddb5dc3f19852a53f977d26c56895f/oslo_middleware-7.0.0.tar.gz", hash = "sha256:3b94fccd32a30e233f150158b80e0cca715366d79c00b0623c394b414a5c8b30", size = 67093, upload-time = "2025-11-19T19:19:01.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/6e725c446a4e187141dde846dbe96c11c0e24916055fb5bd58955e81bb93/oslo_middleware-8.2.0.tar.gz", hash = "sha256:dc53a609c59bb3d92f7364d9b6db8f704e672f5b7ea904c5824ccc1dc189b92b", size = 75602, upload-time = "2026-07-10T13:03:43.712Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/47/b0515778b0a4da8eb7cdfbae42d58651e20ecd1b915af970dd6c18c31afd/oslo_middleware-7.0.0-py3-none-any.whl", hash = "sha256:dcb32f7d50f7951fe367987636404f13180707703b9abe854b8c9eaeb699c255", size = 65879, upload-time = "2025-11-19T19:19:00.558Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fe/2de47424cc03e013e20f0d5679150ce540ebc810e2c243b6870a781520a0/oslo_middleware-8.2.0-py3-none-any.whl", hash = "sha256:cd5af811982a40a1d3c4e4eacc213033c1a231a1893bf498113da4bd45936b31", size = 74880, upload-time = "2026-07-10T13:03:42.475Z" }, ] [[package]] name = "oslo-policy" -version = "4.7.0" +version = "6.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "oslo-config" }, @@ -1138,34 +1149,32 @@ dependencies = [ { name = "requests" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/f8/fe58fb492637d3100b4e77965991e0e80650d35144007f4b12a7b06829e5/oslo_policy-4.7.0.tar.gz", hash = "sha256:984f2414ea805394b1e3af65b172ec1f7c67deafa181b039d02e7408f5dc27cf", size = 120673, upload-time = "2025-11-13T13:51:05.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/1e/71f9edfe473164f185adc3f9c9795678fb74dbf4dd0036468ef3b9b3d2e1/oslo_policy-6.0.1.tar.gz", hash = "sha256:0983259d3919b15caa3755b22fba1451904f60ac20a48656bc61afaf420f7a67", size = 121527, upload-time = "2026-08-24T14:03:35.7Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/25/82ed04c26fe511483fdc07336594194ad1446530b614de3c3efd6795a4d5/oslo_policy-4.7.0-py3-none-any.whl", hash = "sha256:65441442b61ee2fda19129fab70866c74813001edff0e7652a757e4feffc2158", size = 88613, upload-time = "2025-11-13T13:51:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/43/d9/a8f1ccf1b706ade6cf3d7fa229651a023efa6d1b62b950954134f91e8076/oslo_policy-6.0.1-py3-none-any.whl", hash = "sha256:9509b6c824b3da378e5f11014c05c188791508ad92acb68feb05272b09dd2056", size = 89020, upload-time = "2026-08-24T14:03:34.583Z" }, ] [[package]] name = "oslo-privsep" -version = "3.8.0" +version = "3.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, { name = "debtcollector" }, - { name = "eventlet" }, - { name = "greenlet" }, { name = "msgpack" }, { name = "oslo-config" }, { name = "oslo-i18n" }, { name = "oslo-log" }, { name = "oslo-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/ce/075ec43d7c7081e761e5b50429b592af8e6077397bc6f62c5f590d9ee05a/oslo_privsep-3.8.0.tar.gz", hash = "sha256:73c4e0656bddb23f620f916a21b6413ca0034483144a5968bcacedd3a34447f6", size = 49502, upload-time = "2025-08-25T12:47:19.485Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/ba/745a51d54e6c716dfecf97e179885aa3ed52a1f97d7b51f34920d0e672b4/oslo_privsep-3.12.0.tar.gz", hash = "sha256:166edcd4a3e982f4ea851308df6408bc6ad5323fbdd83ad6b2a79faed50b9617", size = 52332, upload-time = "2026-07-10T13:56:42.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/0a/613759825c6ea89bba08f9a564ab20e7c27313c39e2591255651c94b0819/oslo_privsep-3.8.0-py3-none-any.whl", hash = "sha256:657554a3c5fc625cd2bd69b7a840d33cc30ce03e651faf0959c8073114747f83", size = 38747, upload-time = "2025-08-25T12:47:16.466Z" }, + { url = "https://files.pythonhosted.org/packages/4e/17/55c5873d2660431c5fb9978336503b6adeb6bda1451f2680ef7b4558d200/oslo_privsep-3.12.0-py3-none-any.whl", hash = "sha256:ee3eaa5b3fb25e2a5199c5bfb1e3c0f90a0d50644a1cf997281d76400e6a2627", size = 41495, upload-time = "2026-07-10T13:56:41.311Z" }, ] [[package]] name = "oslo-reports" -version = "3.6.0" +version = "3.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -1176,40 +1185,40 @@ dependencies = [ { name = "pbr" }, { name = "psutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/b7/af820b313ff8e77e6cf5d2d873ac2dd8081be88f6a5939ea15c4249f6d91/oslo_reports-3.6.0.tar.gz", hash = "sha256:3dc915adf2843154fceef187ae5e1813c0a38dff17faf3ab8849d4da67e94998", size = 54005, upload-time = "2025-08-25T12:48:06.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/d8/6918517b6703189482522c9384955e237c2e845484b4d1fb8f8c4d8e1777/oslo_reports-3.9.0.tar.gz", hash = "sha256:66e0563d5cb5afc0facba08f48906493d44a9a0487d14a3b02b12fb60ed95513", size = 55809, upload-time = "2026-07-10T12:44:07.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/a0/87fb7b10d461a5991d0529cc71959a9be6fe89d8af132d2ef1974d3a36ff/oslo_reports-3.6.0-py3-none-any.whl", hash = "sha256:4ed0349319f9062ca50a26ab2e4fd233d75235ff57e3447e853f6f38ea4337b9", size = 53159, upload-time = "2025-08-25T12:48:05.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/79/a9f8012f9d162708d8a734efdd4a36d92196398727c9b5a9df2db1f0d684/oslo_reports-3.9.0-py3-none-any.whl", hash = "sha256:6417d4ac55c59d3d6efa7a188b6d2eec6d8f01d971efb1853c2adb4ba7725e3b", size = 54733, upload-time = "2026-07-10T12:44:05.614Z" }, ] [[package]] name = "oslo-rootwrap" -version = "7.8.0" +version = "7.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, + { name = "pbr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/ab/77de0eb0cea79b9995a8d980faf7ea796d32067c0213e975b5991b377414/oslo_rootwrap-7.8.0.tar.gz", hash = "sha256:955aa59d5dbeba4d7c2ccada80139298a3b750ec01805cb19c86a41c959f8ed9", size = 50942, upload-time = "2025-11-13T13:51:20.214Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/c7/f9aea00df0b93d36948d7e8b82e7e7de626e03ee80115cedf523d54c96c7/oslo_rootwrap-7.10.0.tar.gz", hash = "sha256:99c24f763c3f1f0be96652e416a099b486115d9709dcfa73310f48168cf89a44", size = 53469, upload-time = "2026-07-10T13:11:59.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/01/561b9175e570570ce013b6b98404e2dbba7111d23de4d4611b0a1c9b3243/oslo_rootwrap-7.8.0-py3-none-any.whl", hash = "sha256:1ebde5fc2977644dce97ce26247027669a7988121eaa193e4bc90ea032b30ee8", size = 38173, upload-time = "2025-11-13T13:51:19.2Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f9/761b04324c12e516b35776803396d51d7f66646e043a2cca5df07f6b852b/oslo_rootwrap-7.10.0-py3-none-any.whl", hash = "sha256:0e1beb11610f4c0453358d5f5b130f805ead9eb3344f5a51472ea52225ee76b4", size = 40880, upload-time = "2026-07-10T13:11:58.698Z" }, ] [[package]] name = "oslo-serialization" -version = "5.8.0" +version = "5.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "msgpack" }, { name = "oslo-utils" }, - { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/ac/119c430df3a86dc6a664fa864f777b4fd5cc16c50caa1ba3dd3bf10f43ae/oslo_serialization-5.8.0.tar.gz", hash = "sha256:5871a62b23f98cacd5518482941ae6d2a983e2936ed52d543ad08685dc6d2343", size = 35227, upload-time = "2025-08-25T12:50:39.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/f5/2611fb291898fa5f3b41c68e916cb060305cb718a043fdbff25026491fc4/oslo_serialization-5.11.0.tar.gz", hash = "sha256:8326e85a80856c1068007423fcf6fe29dd2fe57a32f5d87fff0120b555b4b67c", size = 37298, upload-time = "2026-07-10T12:47:47.913Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/4c/269503bc1355798e33e56e6c602ed672d1e40bd65485d771da9595779606/oslo_serialization-5.8.0-py3-none-any.whl", hash = "sha256:451e19b9c72e4e7167063eea29e24ad07742d393ff2b54bb8783b9601c155b2c", size = 25766, upload-time = "2025-08-25T12:50:38.253Z" }, + { url = "https://files.pythonhosted.org/packages/2e/12/c62e721591107d15aaab271ed97d93564f77a37225c15148b6c85029d492/oslo_serialization-5.11.0-py3-none-any.whl", hash = "sha256:34d47104d94296488fad23f280ad09b7000102636b3e6f4f120517678ba1b64f", size = 27617, upload-time = "2026-07-10T12:47:46.867Z" }, ] [[package]] name = "oslo-service" -version = "4.4.1" +version = "4.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -1226,9 +1235,9 @@ dependencies = [ { name = "webob" }, { name = "yappi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/f9/794b71269484f0fb776bd53bd80bc6b39500745c26ee8d0c03c889144b6b/oslo_service-4.4.1.tar.gz", hash = "sha256:2f0e306e934f6210c8cabfa68cf86339167df199cb084cf01a9800427086e725", size = 107926, upload-time = "2025-11-19T14:54:22.432Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/9b/ad4836f44713a6bf3f36f4f1965ac1ab756a124dfb40844d5a4b94342bcc/oslo_service-4.8.0.tar.gz", hash = "sha256:5f82357f4c8b20bf40b530262c87b829da4ab1631dfe6e5801ecf6e5d86c60a9", size = 130092, upload-time = "2026-07-31T14:39:01.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/70/76ec072c4afd07b9be2d114a9bce02a12b2065ff03fbefd5b26d73f21a86/oslo_service-4.4.1-py3-none-any.whl", hash = "sha256:6e5922bc0875b1aa98c8fe3dbd7f92725ef28a3100a793e69d813fdcf13fc1bf", size = 102176, upload-time = "2025-11-19T14:54:20.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/47/1d0ee8fc415c183724b5631b1db83fcc5f147900ace1a01e6ace84f43d6e/oslo_service-4.8.0-py3-none-any.whl", hash = "sha256:81501deb989f9801aada393d3e425486cc72295b228bcc7f49bb54caabfd47d5", size = 125105, upload-time = "2026-07-31T14:39:00.691Z" }, ] [package.optional-dependencies] @@ -1239,7 +1248,7 @@ threading = [ [[package]] name = "oslo-upgradecheck" -version = "2.6.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "oslo-config" }, @@ -1248,17 +1257,16 @@ dependencies = [ { name = "oslo-utils" }, { name = "prettytable" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/5d/32e01be549bfd63c55efc9610973ad8eecd0a2195d881755f4739d222dce/oslo_upgradecheck-2.6.0.tar.gz", hash = "sha256:189246e7ef9aa8fa57e5a4df6b2168ff69572571b43bded63faaff676a8542d6", size = 19855, upload-time = "2025-08-25T13:03:03.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/a3/3a5fe5f6e870de5c1a350ec019369b1ee50a99b45973ed318207e4139e8c/oslo_upgradecheck-2.8.0.tar.gz", hash = "sha256:7656ecc54a08191f86d9a21eb9bdf43fb7dc305b9cef4c9aa577a027f2ee3740", size = 21144, upload-time = "2026-07-10T12:47:56.525Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/8d/47873e44a142d625b1371fcdec9f412adaa7aa2d01306b704040ddbb3592/oslo_upgradecheck-2.6.0-py3-none-any.whl", hash = "sha256:460e7400e4963daf627f54c2809af20a122f158255c4d8be579d150ad66eb24e", size = 14425, upload-time = "2025-08-25T13:03:02.609Z" }, + { url = "https://files.pythonhosted.org/packages/c1/04/c5e11e46d38f1557f1ad480f77ca63d219c7a636d1c4ab75446f7d39eecc/oslo_upgradecheck-2.8.0-py3-none-any.whl", hash = "sha256:41462a597102ddf51d2169bdf5d85d1a3eb7be4f2ae483dd4074f8a9965f02a8", size = 14920, upload-time = "2026-07-10T12:47:55.451Z" }, ] [[package]] name = "oslo-utils" -version = "9.2.0" +version = "10.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "debtcollector" }, { name = "iso8601" }, { name = "netaddr" }, { name = "oslo-i18n" }, @@ -1267,37 +1275,46 @@ dependencies = [ { name = "psutil" }, { name = "pyparsing" }, { name = "pyyaml" }, - { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/fe/22b08a1234826f3baace1d46494f764625a6e6753b8e4b3809aa93ae35b3/oslo_utils-9.2.0.tar.gz", hash = "sha256:1f42006de6b9291a0305025f5ccdb8907120d88ed7ebc3784a1472a3328048ab", size = 142524, upload-time = "2025-11-20T15:10:12.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/fd/7915fc0e2c959bcb3dbbe608deeeefe4264d03ba89a4fd75d74524326652/oslo_utils-10.2.0.tar.gz", hash = "sha256:ba839dea2c1eb415e3ee151c4cc688f52e283f59bc1f77cc86772190a8b34259", size = 159551, upload-time = "2026-08-24T13:57:03.827Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/79/e8f828d0743240979b3a85aa6d77ce1250b25f62f0c273f7049a4d8b055c/oslo_utils-9.2.0-py3-none-any.whl", hash = "sha256:2d80bae493b1c3b3b1f43d25410e251579fa839bb1177b820af7a7b9450a7d47", size = 138034, upload-time = "2025-11-20T15:10:11.131Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b4/f2b480738b33cf9e8c04326116d1c6557cf17a87e4414edcefad70bfc4dc/oslo_utils-10.2.0-py3-none-any.whl", hash = "sha256:f33abd2f3c638c00f8736dfdff83a34f433bdb71e3b448d4380809a329917919", size = 154352, upload-time = "2026-08-24T13:57:02.7Z" }, ] [[package]] name = "oslo-versionedobjects" -version = "3.8.0" +version = "3.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "netaddr" }, - { name = "oslo-concurrency" }, { name = "oslo-config" }, { name = "oslo-context" }, { name = "oslo-i18n" }, { name = "oslo-log" }, { name = "oslo-messaging" }, - { name = "oslo-serialization" }, { name = "oslo-utils" }, - { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/7e/d349326371ff424ed58ae7201fe2e153557216df82dc28bbcd2038a0dc1a/oslo_versionedobjects-3.8.0.tar.gz", hash = "sha256:b66e45f62e1d7852183ce570a7f927149b8bf909f218f48ec12d43dcbdb27ffa", size = 154983, upload-time = "2025-08-25T12:48:55.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/de/2881d6845b0a72f54e5a30d90dbe0404193819249eb73179f6d5053673c7/oslo_versionedobjects-3.12.0.tar.gz", hash = "sha256:7bf14e5845d2f27670ef3b6d9c8757b00363f7f9f1404d23ee969ade70f6ab99", size = 177349, upload-time = "2026-08-24T13:56:52.562Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/f8/a40f5180b07c303715077af7e94479fc94e07cfb0738abc5a1b14be08b0e/oslo_versionedobjects-3.8.0-py3-none-any.whl", hash = "sha256:9b0fc25b23e1011ceefa365d0490b47048d077ab6e9d6e8955ff00186491803f", size = 86279, upload-time = "2025-08-25T12:48:54.482Z" }, + { url = "https://files.pythonhosted.org/packages/77/38/2009e4173dedb4d1bb6ea6849284a4b391ec63141dbc85649b0ee6edf70e/oslo_versionedobjects-3.12.0-py3-none-any.whl", hash = "sha256:d5e7c5f6bcde2e8510dfc25764dac2f4325739a059ca1aebd5f160bf783b971a", size = 108861, upload-time = "2026-08-24T13:56:51.258Z" }, +] + +[[package]] +name = "oslotest" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fixtures" }, + { name = "testtools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/32/0ed2a2bc04500d0cc6ec9170a3f120a2574a21a3ed921fab54adece2495b/oslotest-6.1.1.tar.gz", hash = "sha256:5ce97347734f08ca626bb496962a8bc7adbae9693730f5859d9c5293d4a2fd80", size = 34131, upload-time = "2026-04-20T07:55:51.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/65/ff3900e9a80c6d32143e9410d4410435f90ae217eed3b1ac34ae7640637c/oslotest-6.1.1-py3-none-any.whl", hash = "sha256:572a2f2689ecab86062a761951f634a66721d43d8306b38b1cbe65cea7fddedc", size = 30090, upload-time = "2026-04-20T07:55:49.887Z" }, ] [[package]] name = "osprofiler" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "netaddr" }, @@ -1309,24 +1326,23 @@ dependencies = [ { name = "requests" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/a7/1fede5a569035e237f87decb737a2e615b565888a7db7e8d05eb1c483e8f/osprofiler-4.3.0.tar.gz", hash = "sha256:77a8da2b23bb5f9048054bd5ccc45d0ac84574ca8a88ef120b8fac6e8864e24c", size = 95940, upload-time = "2025-05-28T17:58:34.49Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/71/9bd056d116d97edbf137afa4b4575bf4e069ec6be8abf69328ac296b1322/osprofiler-4.4.0.tar.gz", hash = "sha256:6e08df7eef6afc0d2d027813d40e43cb228f0eb3eb3441d61495c2800ebfa320", size = 96231, upload-time = "2026-05-05T14:10:59.309Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/07/01e28443724ddebe1f99d5ffb4bc634ff4651272c9c513db1d74c43b419c/osprofiler-4.3.0-py3-none-any.whl", hash = "sha256:762e6b127a8e198c7121f804a0d44f7d4b5c6310bff76bc4775c75a8445c9bb6", size = 95217, upload-time = "2025-05-28T17:58:33.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/a9a8222cb1733f33008e582cde5e0c3ed5109225301de7eaa6aa3c4ec75a/osprofiler-4.4.0-py3-none-any.whl", hash = "sha256:a7bbcfd4abe9521b31355b04a2ad43ff5d64bfcb2860b48f124e8f92e7168383", size = 95161, upload-time = "2026-05-05T14:10:57.875Z" }, ] [[package]] name = "ovs" -version = "3.6.1" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/dc/46b8b62cc9e70ab7a369916d8db089382254a81c2a69dea59652b87f1fe8/ovs-3.6.1.tar.gz", hash = "sha256:d4a214df1d92c80a132b622573c2e760ffc73faf25950247fb2222e02057023d", size = 161786, upload-time = "2025-11-14T21:16:52.992Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/45/145eefde089bb3e9e7211e4705053f2e572c73255e059a9c7180f1fd73ca/ovs-4.0.0.tar.gz", hash = "sha256:c5fcb3f59383bf055368df6739db9f38c61c94c24086b7936a6b78668b9693cf", size = 155127, upload-time = "2026-08-17T20:01:24.336Z" } [[package]] name = "ovsdbapp" -version = "2.14.1" +version = "2.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fixtures" }, @@ -1334,23 +1350,23 @@ dependencies = [ { name = "ovs" }, { name = "pbr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/70/863d8344ec892912922a7de72692236752f30ca92fdba84b0d7355057b9b/ovsdbapp-2.14.1.tar.gz", hash = "sha256:37eb3a1cb742c455507a5222b203341d6cf28830c79569dd8536cfe070718409", size = 128204, upload-time = "2025-11-24T17:21:03.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/03/08abd8115f35b9b3f4ba31e6022f312ee1e5c594adf3bebc194931aa8b62/ovsdbapp-2.19.0.tar.gz", hash = "sha256:554d091d52553a801756f178d91d108f29fe9936d13955de853c4cdc142d196d", size = 138978, upload-time = "2026-08-20T15:16:18.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/c9/fe6d067f601a8febece82f9ef70f6d470d3496e85349ada6b90de1ccacbb/ovsdbapp-2.14.1-py3-none-any.whl", hash = "sha256:b10ac74543e850c93ea4cb560a94ca530456700f0f7e94faece4c0ed6540c455", size = 145380, upload-time = "2025-11-24T17:21:02.036Z" }, + { url = "https://files.pythonhosted.org/packages/84/15/6567506e3cfa128b1e9058967c65fe9d954874156d5ccead936b5ec3c6f6/ovsdbapp-2.19.0-py3-none-any.whl", hash = "sha256:9c59fdc3ce029564294913d6aa8aa22e37b6c72f762dfe878f915c24d94dc67d", size = 158411, upload-time = "2026-08-20T15:16:17.059Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] name = "paramiko" -version = "4.0.0" +version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bcrypt" }, @@ -1358,9 +1374,9 @@ dependencies = [ { name = "invoke" }, { name = "pynacl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/e7/81fdcbc7f190cdb058cffc9431587eb289833bdd633e2002455ca9bb13d4/paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f", size = 1630743, upload-time = "2025-08-04T01:02:03.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/93/dcc25d52f49022ae6175d15e6bd751f1acc99b98bc61fc55e5155a7be2e7/paramiko-5.0.0.tar.gz", hash = "sha256:36763b5b95c2a0dcfdf1abc48e48156ee425b21efe2f0e787c2dd5a95c0e5e79", size = 1548586, upload-time = "2026-05-09T18:28:52.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9", size = 223932, upload-time = "2025-08-04T01:02:02.029Z" }, + { url = "https://files.pythonhosted.org/packages/82/5b/eadf6d45de38d30ab603f49393b6cd2cbe7e233af8cf90197e32782b68a9/paramiko-5.0.0-py3-none-any.whl", hash = "sha256:b7044611c30140d9a75261653210e2002977b71a0497ff3ba0d98d7edbf62f7c", size = 208919, upload-time = "2026-05-09T18:28:50.295Z" }, ] [[package]] @@ -1386,37 +1402,37 @@ wheels = [ [[package]] name = "pbr" -version = "7.0.3" +version = "7.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/ab/1de9a4f730edde1bdbbc2b8d19f8fa326f036b4f18b2f72cfbea7dc53c26/pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29", size = 135625, upload-time = "2025-11-03T17:04:56.274Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/58/74a362548619561fca4d29dd934b8ad295ba8b6a6cbc09612555a04ecafb/pbr-7.1.1.tar.gz", hash = "sha256:973da2aee9961d7cb278aa74e1829f6daafec9dd8ee8c1655cbf3f5843bfe083", size = 139972, upload-time = "2026-08-24T09:12:04.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/db/61efa0d08a99f897ef98256b03e563092d36cc38dc4ebe4a85020fe40b31/pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b", size = 131898, upload-time = "2025-11-03T17:04:54.875Z" }, + { url = "https://files.pythonhosted.org/packages/3d/05/01750d3f53966e7b73ce6361f4d6ea99ae01b98f8ec23d142ba00b3e428d/pbr-7.1.1-py2.py3-none-any.whl", hash = "sha256:8e36e7b36cd0d081cca95c7b50d5760b17d7dd7d58a86044186d916e8be1f09d", size = 136517, upload-time = "2026-08-24T09:12:03.443Z" }, ] [[package]] name = "pecan" -version = "1.7.0" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "setuptools" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/57/0eb9798962c4bf98819efca45293fb3d481ba748f1e83af165aa342cf2af/pecan-1.7.0.tar.gz", hash = "sha256:7de15bf4a2600dc584bed0f25431dfed6bf20a7a5bc935a48d2ce50063339815", size = 123025, upload-time = "2025-07-07T16:04:43.433Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/7a/1a69bbc37a5150d17d2955322797f0b3f1f2ca9757e53761c339caa8a4df/pecan-1.8.0.tar.gz", hash = "sha256:2f9fbcea86e8dbf1a2d28954225547634a289db7201e7764516a5dce86c116de", size = 122667, upload-time = "2026-03-12T21:57:27.11Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/3b/fbf52f6b1c57a92fd0c0fe60d25dce8219e368f7a35d2fec0becb2cf90cb/pecan-1.7.0-py3-none-any.whl", hash = "sha256:3dae9015f5a4e02375651b6b7d6110209c70aea0c827f309e421ea1c2fbb4ec2", size = 144590, upload-time = "2025-07-07T16:04:42.358Z" }, + { url = "https://files.pythonhosted.org/packages/c8/75/df5f578fd3b6b422e2edd5c9401ed3b533778d66e3d5761fe5138a851d17/pecan-1.8.0-py3-none-any.whl", hash = "sha256:eb3e2833d4c0295163d9cce57adc2736f5f8116946a4e68c2e4e6297fc1865de", size = 144352, upload-time = "2026-03-12T21:57:26.112Z" }, ] [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.11.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/bb/ebc6636e1ae41314f796ebb7215fd28febb45f9aac72f2b04cb74b5071dc/platformdirs-4.11.4.tar.gz", hash = "sha256:f3373be828247211d0febabea97e238c3dfde8a60b3c90c32756fb52cb21556d", size = 34079, upload-time = "2026-08-24T14:53:49.676Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/0ff05fcd2938fb58ad9219bd54135968342d214737e012d62d43f06a2dd6/platformdirs-4.11.4-py3-none-any.whl", hash = "sha256:e34ff91a24bcddc6d939b878bdf3f5c437c9c46fe9e212b1bf455fdf1ee57586", size = 23741, upload-time = "2026-08-24T14:53:48.406Z" }, ] [[package]] @@ -1430,124 +1446,136 @@ wheels = [ [[package]] name = "prettytable" -version = "3.17.0" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/45/b0847d88d6cfeb4413566738c8bbf1e1995fad3d42515327ff32cc1eb578/prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0", size = 67892, upload-time = "2025-11-14T17:33:20.212Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/74/ba08d81e668ccfe8658d7520a307e63c19862c08eb4ccb26f356c5239a7a/prettytable-3.18.0.tar.gz", hash = "sha256:439217116152244369caf3d9f1caf2f9fe29b03bd79e88d2928c8e718c95d680", size = 76373, upload-time = "2026-06-22T16:07:50.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433, upload-time = "2025-11-14T17:33:19.093Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl", hash = "sha256:b3346e0e6f79180833aebaac088ae926340586cf6d7d991b9eb125b65f72313a", size = 37357, upload-time = "2026-06-22T16:07:48.595Z" }, ] [[package]] name = "prometheus-client" -version = "0.23.1" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/73/f1334c29c2af4cd9dba6c7817e61b611bd0215e2eb5565c6064a4de18802/prometheus_client-0.26.0.tar.gz", hash = "sha256:04a91bcf94e2cf74a44a1a874d651a2e853ed354b6e822f3b7487751465d5c2b", size = 92910, upload-time = "2026-07-24T19:36:41.893Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/a3/b69efbf4143b5b9859b977770bbbabcc2796b702fa69dc40271e45cd5a56/prometheus_client-0.26.0-py3-none-any.whl", hash = "sha256:fa93d06737aa02bacd05794768508bb97d2fbee28cb3bca04eaae92f0ca953d6", size = 64494, upload-time = "2026-07-24T19:36:40.854Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.53" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, + { url = "https://files.pythonhosted.org/packages/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, ] [[package]] name = "psutil" -version = "7.1.3" +version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "pycadf" -version = "4.0.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "oslo-config" }, { name = "oslo-serialization" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/09/c22a17ecfde3c6af5d5d2c5ebcd0ba81d1b6cdf9736a99ea7341ffa3fb33/pycadf-4.0.1.tar.gz", hash = "sha256:b015f1e4cfb6bd968bb8e23f903593328f0d82e1922902447197f4862f259d2f", size = 252343, upload-time = "2025-01-29T14:56:07.956Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/e7/dc9d02c4c2fe2570552273c778de7fdab86993ad2bea1bac82f576dd1218/pycadf-4.1.0.tar.gz", hash = "sha256:7f9be03e774ce3cc97675aac7c807c017cad6775961da7e9508f75bcc9f43267", size = 249070, upload-time = "2026-07-09T12:09:57.023Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/26/73c8a7b6be652a704de57bcc0811d1b9717b3ce693b99d8dc48c102a7cf0/pycadf-4.0.1-py3-none-any.whl", hash = "sha256:eb15577f6771392fa285ec0b2996434b1a91e91e347659878817927b92dbad0f", size = 44176, upload-time = "2025-01-29T14:56:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/de/3b/83b1a93195da99b4b9320d4599f3e677003ea07f2110d3ded2744d24ce17/pycadf-4.1.0-py3-none-any.whl", hash = "sha256:505031bf95065f9b54c280772ff8fa6c5dd12b58357e625cea886b3e6828096f", size = 44280, upload-time = "2026-07-09T12:09:55.829Z" }, ] [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pygments" -version = "2.19.2" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] [[package]] name = "pynacl" -version = "1.6.1" +version = "1.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/46/aeca065d227e2265125aea590c9c47fbf5786128c9400ee0eb7c88931f06/pynacl-1.6.1.tar.gz", hash = "sha256:8d361dac0309f2b6ad33b349a56cd163c98430d409fa503b10b70b3ad66eaa1d", size = 3506616, upload-time = "2025-11-10T16:02:13.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/41/3cfb3b4f3519f6ff62bf71bf1722547644bcfb1b05b8fdbdc300249ba113/pynacl-1.6.1-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:a6f9fd6d6639b1e81115c7f8ff16b8dedba1e8098d2756275d63d208b0e32021", size = 387591, upload-time = "2025-11-10T16:01:49.1Z" }, - { url = "https://files.pythonhosted.org/packages/18/21/b8a6563637799f617a3960f659513eccb3fcc655d5fc2be6e9dc6416826f/pynacl-1.6.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e49a3f3d0da9f79c1bec2aa013261ab9fa651c7da045d376bd306cf7c1792993", size = 798866, upload-time = "2025-11-10T16:01:55.688Z" }, - { url = "https://files.pythonhosted.org/packages/e8/6c/dc38033bc3ea461e05ae8f15a81e0e67ab9a01861d352ae971c99de23e7c/pynacl-1.6.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7713f8977b5d25f54a811ec9efa2738ac592e846dd6e8a4d3f7578346a841078", size = 1398001, upload-time = "2025-11-10T16:01:57.101Z" }, - { url = "https://files.pythonhosted.org/packages/9f/05/3ec0796a9917100a62c5073b20c4bce7bf0fea49e99b7906d1699cc7b61b/pynacl-1.6.1-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a3becafc1ee2e5ea7f9abc642f56b82dcf5be69b961e782a96ea52b55d8a9fc", size = 834024, upload-time = "2025-11-10T16:01:50.228Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b7/ae9982be0f344f58d9c64a1c25d1f0125c79201634efe3c87305ac7cb3e3/pynacl-1.6.1-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ce50d19f1566c391fedc8dc2f2f5be265ae214112ebe55315e41d1f36a7f0a9", size = 1436766, upload-time = "2025-11-10T16:01:51.886Z" }, - { url = "https://files.pythonhosted.org/packages/b4/51/b2ccbf89cf3025a02e044dd68a365cad593ebf70f532299f2c047d2b7714/pynacl-1.6.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:543f869140f67d42b9b8d47f922552d7a967e6c116aad028c9bfc5f3f3b3a7b7", size = 817275, upload-time = "2025-11-10T16:01:53.351Z" }, - { url = "https://files.pythonhosted.org/packages/a8/6c/dd9ee8214edf63ac563b08a9b30f98d116942b621d39a751ac3256694536/pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a2bb472458c7ca959aeeff8401b8efef329b0fc44a89d3775cffe8fad3398ad8", size = 1401891, upload-time = "2025-11-10T16:01:54.587Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c1/97d3e1c83772d78ee1db3053fd674bc6c524afbace2bfe8d419fd55d7ed1/pynacl-1.6.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3206fa98737fdc66d59b8782cecc3d37d30aeec4593d1c8c145825a345bba0f0", size = 772291, upload-time = "2025-11-10T16:01:58.111Z" }, - { url = "https://files.pythonhosted.org/packages/4d/ca/691ff2fe12f3bb3e43e8e8df4b806f6384593d427f635104d337b8e00291/pynacl-1.6.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:53543b4f3d8acb344f75fd4d49f75e6572fce139f4bfb4815a9282296ff9f4c0", size = 1370839, upload-time = "2025-11-10T16:01:59.252Z" }, - { url = "https://files.pythonhosted.org/packages/30/27/06fe5389d30391fce006442246062cc35773c84fbcad0209fbbf5e173734/pynacl-1.6.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:319de653ef84c4f04e045eb250e6101d23132372b0a61a7acf91bac0fda8e58c", size = 791371, upload-time = "2025-11-10T16:02:01.075Z" }, - { url = "https://files.pythonhosted.org/packages/2c/7a/e2bde8c9d39074a5aa046c7d7953401608d1f16f71e237f4bef3fb9d7e49/pynacl-1.6.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:262a8de6bba4aee8a66f5edf62c214b06647461c9b6b641f8cd0cb1e3b3196fe", size = 1363031, upload-time = "2025-11-10T16:02:02.656Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b6/63fd77264dae1087770a1bb414bc604470f58fbc21d83822fc9c76248076/pynacl-1.6.1-cp38-abi3-win32.whl", hash = "sha256:9fd1a4eb03caf8a2fe27b515a998d26923adb9ddb68db78e35ca2875a3830dde", size = 226585, upload-time = "2025-11-10T16:02:07.116Z" }, - { url = "https://files.pythonhosted.org/packages/12/c8/b419180f3fdb72ab4d45e1d88580761c267c7ca6eda9a20dcbcba254efe6/pynacl-1.6.1-cp38-abi3-win_amd64.whl", hash = "sha256:a569a4069a7855f963940040f35e87d8bc084cb2d6347428d5ad20550a0a1a21", size = 238923, upload-time = "2025-11-10T16:02:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/35/76/c34426d532e4dce7ff36e4d92cb20f4cbbd94b619964b93d24e8f5b5510f/pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf", size = 183970, upload-time = "2025-11-10T16:02:05.786Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, ] [[package]] name = "pyopenssl" -version = "25.3.0" +version = "26.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/e8/7325d258199b159eb2c03fe32107533e2832e70e63f4fb88a6aa00023201/pyopenssl-26.4.0.tar.gz", hash = "sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7", size = 182046, upload-time = "2026-08-01T19:50:50.512Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/2cf6d3fa2fae5c79e1ed9960c0d42badd0f94d81dd12b50604cdc839e648/pyopenssl-26.4.0-py3-none-any.whl", hash = "sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c", size = 56026, upload-time = "2026-08-01T19:50:48.94Z" }, ] [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -1559,22 +1587,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, ] -[[package]] -name = "pyreadline3" -version = "3.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, -] - [[package]] name = "pyroute2" -version = "0.9.5" +version = "0.9.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/1019ddc278549fb7e64a16d7775e0f7d981135f762b8706e583414d655e3/pyroute2-0.9.5.tar.gz", hash = "sha256:a198ccbe545b031b00b10da4b44df33d548db04af944be8107c05a215ba03872", size = 969492, upload-time = "2025-11-02T18:42:49.603Z" } +dependencies = [ + { name = "win-inet-pton", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/3c/cae3aa8a07522d4fd625958f690ab6eb4ffbd9c94e30e2995f585fded630/pyroute2-0.9.6.tar.gz", hash = "sha256:6bc5e2ea9a372ded682b4ede4028ba00236bd6e35b42d833f39a96b219ef1db2", size = 478486, upload-time = "2026-04-15T18:26:07.408Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/66/995c65e7566543e941705f9b1b99ebfd7f9d3ed67c8eb7cb47f30168dc72/pyroute2-0.9.5-py3-none-any.whl", hash = "sha256:e7d485ce8274cbf473e9092fa65585faf8a3df5fe05ecf497cb9c5b1516ba09f", size = 480641, upload-time = "2025-11-02T18:42:45.75Z" }, + { url = "https://files.pythonhosted.org/packages/14/f5/77292e847cb2bcd94f0e7be214ad09972de5db6a6914e47117293ed0f4a8/pyroute2-0.9.6-py3-none-any.whl", hash = "sha256:3334091326e560a506635449af03b26920d22d4e5a7996aed354363d106fcef8", size = 483440, upload-time = "2026-04-15T18:26:03.14Z" }, ] [[package]] @@ -1595,16 +1617,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -1633,7 +1655,7 @@ wheels = [ [[package]] name = "python-designateclient" -version = "6.3.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cliff" }, @@ -1647,14 +1669,14 @@ dependencies = [ { name = "requests" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/03/96a90b0a83c8e033a8d8d679e457acad18439f5de4a8a1b4a826ff02c412/python_designateclient-6.3.0.tar.gz", hash = "sha256:9aabf25e5ff69b15fb6209f9851c507d9499e63c6926245672ff0e495986e778", size = 72386, upload-time = "2025-05-16T13:29:09.286Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/66/9e39949850a7f086641508805ebeab1553451eabde353e05788288e3e80b/python_designateclient-7.0.0.tar.gz", hash = "sha256:d9a1086e7bf81f4034ca0ec7a243cbd8b344bfb6095e2903c553cc3807d2bed2", size = 75902, upload-time = "2026-07-01T11:50:13.876Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/91/dccb030a135eb2d766717a3fca33fa8979540157e54fc8efc32f793d1603/python_designateclient-6.3.0-py3-none-any.whl", hash = "sha256:e551e73b9eb0a76d9f75b5870d8f5a2d5eb1bba6e536c433822e35f661b98f8b", size = 95717, upload-time = "2025-05-16T13:29:07.871Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c5/5385cdef7fb130495cb4a8ee1f16496cba32fff48f4c5b2f6aa3fc4837bd/python_designateclient-7.0.0-py3-none-any.whl", hash = "sha256:ab587ffb24f1bab7b5f36339c52ad4694c302c3fdaf54950b79564083ca94f4c", size = 99837, upload-time = "2026-07-01T11:50:12.777Z" }, ] [[package]] name = "python-keystoneclient" -version = "5.7.0" +version = "5.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -1668,14 +1690,14 @@ dependencies = [ { name = "requests" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/29/3775d7a722924a72208753a8aa5ddb0a58de24f5a5dd287cc9a0f66038e4/python_keystoneclient-5.7.0.tar.gz", hash = "sha256:8ce7bf1c8cddca6d7140fc76918b44eddf1d64040a60cb8ff7059136104d4ceb", size = 322387, upload-time = "2025-08-28T09:50:01.249Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/ef/c8c68219a2bf9f296ad18cb0b9804c45adfdceee72d51684225488746262/python_keystoneclient-5.8.0.tar.gz", hash = "sha256:3ca87c67c404298ce862310b569f545a58acf75cd5685094c82f35320b3a355d", size = 322844, upload-time = "2026-02-27T15:36:24.33Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/d1/eb39a6e544f5f789fd44c09274a8b1b08a86e3e7ac0e016d07ceb94c0b42/python_keystoneclient-5.7.0-py3-none-any.whl", hash = "sha256:2ec19994ba8b8b16fe6cf4ac45c3f2180a22e203d1b3aa977a34a0160caae0b6", size = 397216, upload-time = "2025-08-28T09:49:59.798Z" }, + { url = "https://files.pythonhosted.org/packages/be/35/483db648710bd0aeefd505db288490c229fef0e1fb7b78d8f277e2dc375b/python_keystoneclient-5.8.0-py3-none-any.whl", hash = "sha256:0cc96cc719bbd8b1ca8fda71e38017dba3e87ebe3b479fcc9261a67e30b21b85", size = 397325, upload-time = "2026-02-27T15:36:23.21Z" }, ] [[package]] name = "python-neutronclient" -version = "11.6.0" +version = "13.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cliff" }, @@ -1683,7 +1705,6 @@ dependencies = [ { name = "keystoneauth1" }, { name = "netaddr" }, { name = "openstacksdk" }, - { name = "os-client-config" }, { name = "osc-lib" }, { name = "oslo-i18n" }, { name = "oslo-log" }, @@ -1693,14 +1714,14 @@ dependencies = [ { name = "python-keystoneclient" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/15/96f2f42df4c1d6873c89a0cae2ba3b98f83273e965421eb11b7dbb257b4d/python_neutronclient-11.6.0.tar.gz", hash = "sha256:3c6958088d18c8676a10abf9d94b8dbf1a984741cbb988554f216880797e072f", size = 212450, upload-time = "2025-07-07T11:11:58.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/c4/e4eb2270cc288875f63339e32be98ac47bcb86e7913232149a9d483e7dbc/python_neutronclient-13.0.0.tar.gz", hash = "sha256:c5fd856adf3dc02cc5f31f9a76f4591d50af48cdfaad25b6e99b1291b543f95a", size = 179680, upload-time = "2026-07-09T08:41:21.028Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/76/487c81f1e07049faa110dd22cfa27f68644e933154340a6c750958dd73a9/python_neutronclient-11.6.0-py3-none-any.whl", hash = "sha256:df37f80a61ed0fb068551a120bb26ead0cefefdf101bf59693c556f4a2828229", size = 296139, upload-time = "2025-07-07T11:11:56.572Z" }, + { url = "https://files.pythonhosted.org/packages/da/27/f37c6ef70518438f6f1a9f28430c71616c2289024011b7d2b0ade69c205d/python_neutronclient-13.0.0-py3-none-any.whl", hash = "sha256:b1a64ef5f274a1fdbf5acf96b981847ed31ec8ada022e137a5953efa91bf4af9", size = 235780, upload-time = "2026-07-09T08:41:19.757Z" }, ] [[package]] name = "python-novaclient" -version = "18.11.0" +version = "18.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "iso8601" }, @@ -1712,19 +1733,9 @@ dependencies = [ { name = "prettytable" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/8a/ac14136fe0a549416b69e6111d6e45c7d821508037922baefa2d54b661f0/python_novaclient-18.11.0.tar.gz", hash = "sha256:0a31ae20779d4cd171bb29c1fe4e6b22b9c7d97c79670db5149bbdfac03f57dc", size = 340209, upload-time = "2025-09-01T13:31:59.62Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/86/6c6d5230eade1cdd10456d08108f783b00394edace0f1f20e5177169ba98/python_novaclient-18.13.0.tar.gz", hash = "sha256:812272fa36fae042c5c195aaeeaf0e5df2ca77edcb9c9e1fd66943f39d32bc4b", size = 340695, upload-time = "2026-05-13T09:20:40.796Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/1f/f944e5f61083af25176b46296522085b0faa17381d0251fd9c0f9f6288d0/python_novaclient-18.11.0-py3-none-any.whl", hash = "sha256:fc4ce032a6ce0fb911f4c640d260318d770ee171eedce7c12f887307fc24dc0c", size = 335871, upload-time = "2025-09-01T13:31:57.87Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/96/36/f070b4e8ce76888855a9b405bfd7f07a97c43db155d01524f0802bba2ff1/python_novaclient-18.13.0-py3-none-any.whl", hash = "sha256:e46abb6d2a77c8f547691a9f49e66216dc03847bc2b0d1884cefba58a0bca750", size = 336073, upload-time = "2026-05-13T09:20:39.308Z" }, ] [[package]] @@ -1761,16 +1772,16 @@ wheels = [ [[package]] name = "repoze-lru" -version = "0.7" +version = "0.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz", hash = "sha256:0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77", size = 19591, upload-time = "2017-09-07T05:01:00.949Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/19/727d5c904ea513a6a9044bf43beaa2c5e632e017b1e0ab71e9d111d20967/repoze_lru-0.8.tar.gz", hash = "sha256:a252408cd93fe670c88d6665b96fe5d42e071dba2507a1f21a1e609ae4fa891a", size = 22169, upload-time = "2026-05-16T16:57:28.315Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/30/6cc0c95f0b59ad4b3b9163bff7cdcf793cc96fac64cf398ff26271f5cf5e/repoze.lru-0.7-py3-none-any.whl", hash = "sha256:f77bf0e1096ea445beadd35f3479c5cff2aa1efe604a133e67150bc8630a62ea", size = 10985, upload-time = "2017-09-07T05:01:00.007Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a1/cc0848069e3937651582c74db0fdddda96f853060a25617754a264913afe/repoze_lru-0.8-py3-none-any.whl", hash = "sha256:979a30d2e567e31f292009ba4467aa444c89ee0da3e3013980c35f1fb4f19d99", size = 6379, upload-time = "2026-05-16T16:57:27.257Z" }, ] [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1778,9 +1789,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -1794,27 +1805,27 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] [[package]] name = "rich-argparse" -version = "1.7.2" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/f7/1c65e0245d4c7009a87ac92908294a66e7e7635eccf76a68550f40c6df80/rich_argparse-1.7.2.tar.gz", hash = "sha256:64fd2e948fc96e8a1a06e0e72c111c2ce7f3af74126d75c0f5f63926e7289cd1", size = 38500, upload-time = "2025-11-01T10:35:44.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/e5/1064c43203a357d668cd42435f7a15fe6af51512d85b2104fecb937aa861/rich_argparse-1.8.0.tar.gz", hash = "sha256:679df3d832fa94ad6e4bdb07ded088cd7ea2dddc58ae9b2b46346a40b06cbc0c", size = 38940, upload-time = "2026-05-01T15:18:43.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/80/97b6f357ac458d9ad9872cc3183ca09ef7439ac89e030ea43053ba1294b6/rich_argparse-1.7.2-py3-none-any.whl", hash = "sha256:0559b1f47a19bbeb82bf15f95a057f99bcbbc98385532f57937f9fc57acc501a", size = 25476, upload-time = "2025-11-01T10:35:42.681Z" }, + { url = "https://files.pythonhosted.org/packages/0b/35/1cceccc5fcb50fa2ed53e2aa278cd032f3902682a73e763fb1ac3be8e6fa/rich_argparse-1.8.0-py3-none-any.whl", hash = "sha256:d2a3ce7854654e2253c578763ab0a32f05016f23a55fadba7b9a91b6c0e92142", size = 25616, upload-time = "2026-05-01T15:18:42.395Z" }, ] [[package]] @@ -1832,25 +1843,25 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.29.0" +version = "2026.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/33/23b3b3419b6a3e0f559c7c0d2ca8fc1b9448382b25245033788785921332/rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359", size = 69359, upload-time = "2025-11-16T14:50:39.532Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/50/bc0e6e736d94e420df79be4deb5c9476b63165c87bb8f19ef75d100d21b3/rpds_py-0.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0891cfd8db43e085c0ab93ab7e9b0c8fee84780d436d3b266b113e51e79f954", size = 376000, upload-time = "2025-11-16T14:48:19.141Z" }, - { url = "https://files.pythonhosted.org/packages/3e/3a/46676277160f014ae95f24de53bed0e3b7ea66c235e7de0b9df7bd5d68ba/rpds_py-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3897924d3f9a0361472d884051f9a2460358f9a45b1d85a39a158d2f8f1ad71c", size = 360575, upload-time = "2025-11-16T14:48:20.443Z" }, - { url = "https://files.pythonhosted.org/packages/75/ba/411d414ed99ea1afdd185bbabeeaac00624bd1e4b22840b5e9967ade6337/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21deb8e0d1571508c6491ce5ea5e25669b1dd4adf1c9d64b6314842f708b5d", size = 392159, upload-time = "2025-11-16T14:48:22.12Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b1/e18aa3a331f705467a48d0296778dc1fea9d7f6cf675bd261f9a846c7e90/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9efe71687d6427737a0a2de9ca1c0a216510e6cd08925c44162be23ed7bed2d5", size = 410602, upload-time = "2025-11-16T14:48:23.563Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6c/04f27f0c9f2299274c76612ac9d2c36c5048bb2c6c2e52c38c60bf3868d9/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f65470919dc189c833e86b2c4bd21bd355f98436a2cef9e0a9a92aebc8e57e", size = 515808, upload-time = "2025-11-16T14:48:24.949Z" }, - { url = "https://files.pythonhosted.org/packages/83/56/a8412aa464fb151f8bc0d91fb0bb888adc9039bd41c1c6ba8d94990d8cf8/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:def48ff59f181130f1a2cb7c517d16328efac3ec03951cca40c1dc2049747e83", size = 416015, upload-time = "2025-11-16T14:48:26.782Z" }, - { url = "https://files.pythonhosted.org/packages/04/4c/f9b8a05faca3d9e0a6397c90d13acb9307c9792b2bff621430c58b1d6e76/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7bd570be92695d89285a4b373006930715b78d96449f686af422debb4d3949", size = 395325, upload-time = "2025-11-16T14:48:28.055Z" }, - { url = "https://files.pythonhosted.org/packages/34/60/869f3bfbf8ed7b54f1ad9a5543e0fdffdd40b5a8f587fe300ee7b4f19340/rpds_py-0.29.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:5a572911cd053137bbff8e3a52d31c5d2dba51d3a67ad902629c70185f3f2181", size = 410160, upload-time = "2025-11-16T14:48:29.338Z" }, - { url = "https://files.pythonhosted.org/packages/91/aa/e5b496334e3aba4fe4c8a80187b89f3c1294c5c36f2a926da74338fa5a73/rpds_py-0.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d583d4403bcbf10cffc3ab5cee23d7643fcc960dff85973fd3c2d6c86e8dbb0c", size = 425309, upload-time = "2025-11-16T14:48:30.691Z" }, - { url = "https://files.pythonhosted.org/packages/85/68/4e24a34189751ceb6d66b28f18159922828dd84155876551f7ca5b25f14f/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:070befbb868f257d24c3bb350dbd6e2f645e83731f31264b19d7231dd5c396c7", size = 574644, upload-time = "2025-11-16T14:48:31.964Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/474a005ea4ea9c3b4f17b6108b6b13cebfc98ebaff11d6e1b193204b3a93/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fc935f6b20b0c9f919a8ff024739174522abd331978f750a74bb68abd117bd19", size = 601605, upload-time = "2025-11-16T14:48:33.252Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b1/c56f6a9ab8c5f6bb5c65c4b5f8229167a3a525245b0773f2c0896686b64e/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c5a8ecaa44ce2d8d9d20a68a2483a74c07f05d72e94a4dff88906c8807e77b0", size = 564593, upload-time = "2025-11-16T14:48:34.643Z" }, - { url = "https://files.pythonhosted.org/packages/b3/13/0494cecce4848f68501e0a229432620b4b57022388b071eeff95f3e1e75b/rpds_py-0.29.0-cp312-cp312-win32.whl", hash = "sha256:ba5e1aeaf8dd6d8f6caba1f5539cddda87d511331714b7b5fc908b6cfc3636b7", size = 223853, upload-time = "2025-11-16T14:48:36.419Z" }, - { url = "https://files.pythonhosted.org/packages/1f/6a/51e9aeb444a00cdc520b032a28b07e5f8dc7bc328b57760c53e7f96997b4/rpds_py-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f6134faf54b3cb83375db0f113506f8b7770785be1f95a631e7e2892101977", size = 239895, upload-time = "2025-11-16T14:48:37.956Z" }, - { url = "https://files.pythonhosted.org/packages/d1/d4/8bce56cdad1ab873e3f27cb31c6a51d8f384d66b022b820525b879f8bed1/rpds_py-0.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:b016eddf00dca7944721bf0cd85b6af7f6c4efaf83ee0b37c4133bd39757a8c7", size = 230321, upload-time = "2025-11-16T14:48:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, ] [[package]] @@ -1873,11 +1884,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.9.0" +version = "84.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, ] [[package]] @@ -1898,25 +1909,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] +[[package]] +name = "soupsieve" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/99/a6ca3beb3ccacb41fb3321d8a60e5566f9e6467601ef8eba6a17e1b89778/soupsieve-2.9.2.tar.gz", hash = "sha256:4a55d8cf158a9c2e587fa4922f1bbb91d68ac829e2d6f25403a85747c71daf74", size = 122445, upload-time = "2026-08-07T00:57:24.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/dc/ad025c1ee131eba60c69f4dd5779b18fcf1e6b21a343e2162a84d5d133c7/soupsieve-2.9.2-py3-none-any.whl", hash = "sha256:8089a26fd974ca7a1f30276d3d8492ab266ab15af581642dfe8aa162e0c1c823", size = 37370, upload-time = "2026-08-07T00:57:23.524Z" }, +] + [[package]] name = "sqlalchemy" -version = "2.0.44" +version = "2.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637, upload-time = "2026-08-11T19:07:09.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/c4/59c7c9b068e6813c898b771204aad36683c96318ed12d4233e1b18762164/sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", size = 2139675, upload-time = "2025-10-10T16:03:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ae/eeb0920537a6f9c5a3708e4a5fc55af25900216bdb4847ec29cfddf3bf3a/sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", size = 2127726, upload-time = "2025-10-10T16:03:35.934Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d5/2ebbabe0379418eda8041c06b0b551f213576bfe4c2f09d77c06c07c8cc5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", size = 3327603, upload-time = "2025-10-10T15:35:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/5aa65852dadc24b7d8ae75b7efb8d19303ed6ac93482e60c44a585930ea5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", size = 3337842, upload-time = "2025-10-10T15:43:45.431Z" }, - { url = "https://files.pythonhosted.org/packages/41/92/648f1afd3f20b71e880ca797a960f638d39d243e233a7082c93093c22378/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", size = 3264558, upload-time = "2025-10-10T15:35:29.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/cf/e27d7ee61a10f74b17740918e23cbc5bc62011b48282170dc4c66da8ec0f/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", size = 3301570, upload-time = "2025-10-10T15:43:48.407Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3d/3116a9a7b63e780fb402799b6da227435be878b6846b192f076d2f838654/sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", size = 2103447, upload-time = "2025-10-10T15:03:21.678Z" }, - { url = "https://files.pythonhosted.org/packages/25/83/24690e9dfc241e6ab062df82cc0df7f4231c79ba98b273fa496fb3dd78ed/sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", size = 2130912, upload-time = "2025-10-10T15:03:24.656Z" }, - { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d5/1b77a026d161f98a08f11af1a5f6c47b98ee7c7e2648af525a1004826c78/sqlalchemy-2.0.52-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727", size = 2170940, upload-time = "2026-08-11T20:58:11.25Z" }, + { url = "https://files.pythonhosted.org/packages/54/bd/f444444adb37b5d53753fb1730ee7a421628e2e3b756c4da461af7e6394a/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee", size = 3383415, upload-time = "2026-08-11T21:02:38.534Z" }, + { url = "https://files.pythonhosted.org/packages/be/57/2eadf93a552568c57e8680b7e58bb5e9770d80942a1bdbaf4f2f63f0d7c8/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b", size = 3398577, upload-time = "2026-08-11T21:16:59.092Z" }, + { url = "https://files.pythonhosted.org/packages/15/c3/2887cf9dd111d1fbf05d22165b404c221ef43e029f7a2695e7302f27a7cc/sqlalchemy-2.0.52-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37a4d548327b6cab9c7d8cdb4e0e82feabee0110c4d150059068e2d1cfbd99ee", size = 3328225, upload-time = "2026-08-11T21:02:40.183Z" }, + { url = "https://files.pythonhosted.org/packages/02/0f/466bdf9e1feeeef5587f868c187d8687e21ff8c85b1775e9041130181132/sqlalchemy-2.0.52-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e49f51a5d59857a7a0dcaf9469febf7197d9394bd88f00d69c2c4e848112cdbf", size = 3357374, upload-time = "2026-08-11T21:17:01.076Z" }, + { url = "https://files.pythonhosted.org/packages/22/20/5c2b4583904af4173076dda1c9e53c9e2ffc7a702d2efde0216bbacbf7cb/sqlalchemy-2.0.52-cp312-cp312-win32.whl", hash = "sha256:afda3ec521d0517d0de783fc70030775841900896d832de5bbd066549290470e", size = 2129366, upload-time = "2026-08-11T21:14:50.991Z" }, + { url = "https://files.pythonhosted.org/packages/ed/06/543dab8ef62d4e9fb96fb31a30c2b8b14a8763bccf48d428294d6b3041c0/sqlalchemy-2.0.52-cp312-cp312-win_amd64.whl", hash = "sha256:2d5e53e36e37129fe0be8b9d08b6e4052c10a963ee6cda56c8c10dcc194b99ca", size = 2157344, upload-time = "2026-08-11T21:14:52.453Z" }, + { url = "https://files.pythonhosted.org/packages/b3/3f/3582293d1e185e71d19d7c731c3e2ee20ba21981c4a1115c0806c1f62120/sqlalchemy-2.0.52-py3-none-any.whl", hash = "sha256:3b81b8363a919ce53453591cdb93702e6bd54ade6c4fa2f468fc053baee5ed89", size = 1950700, upload-time = "2026-08-11T20:47:21.603Z" }, +] + +[package.optional-dependencies] +asyncio = [ + { name = "greenlet" }, ] [[package]] @@ -1930,59 +1954,54 @@ wheels = [ [[package]] name = "stevedore" -version = "5.6.0" +version = "5.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/5b/496f8abebd10c3301129abba7ddafd46c71d799a70c44ab080323987c4c9/stevedore-5.6.0.tar.gz", hash = "sha256:f22d15c6ead40c5bbfa9ca54aa7e7b4a07d59b36ae03ed12ced1a54cf0b51945", size = 516074, upload-time = "2025-11-20T10:06:07.264Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/a1/3b8ed9c1fc3aa6eebb57732d924ddaa0500ecc3b638d0454816320994383/stevedore-5.9.1.tar.gz", hash = "sha256:e97a2667923efda926e8713fde6a73616df68210a3cbc6f02b48967b676fd8bf", size = 518111, upload-time = "2026-08-20T15:25:14.754Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" }, + { url = "https://files.pythonhosted.org/packages/c5/97/bba6e7ec2f5498b9dcb7b1b6400086b80ae5a8ebaff4b25e8c8add75f439/stevedore-5.9.1-py3-none-any.whl", hash = "sha256:5c8ff3a9f336cc1a06ac0f597bc79d11a2f950bfd32e290ca56b5a301fafafbf", size = 54931, upload-time = "2026-08-20T15:25:13.602Z" }, ] [[package]] name = "tenacity" -version = "9.1.2" +version = "9.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, ] [[package]] name = "testresources" -version = "2.0.2" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pbr" }, +sdist = { url = "https://files.pythonhosted.org/packages/5f/f4/5e0ecad96a86bc58f7833fc26ae623a67c49148f56327b11dfddf30aa176/testresources-2.1.2.tar.gz", hash = "sha256:a2b1266fd6cb89295dbb79e3442e5ff1c27db35a0b62b84c7846002676d12ae2", size = 37654, upload-time = "2026-04-21T20:52:52.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/9e/3fb8523e5252cf07f6ae3c8ba685de9a03405b0ddffdd8e3e2a621e40b07/testresources-2.1.2-py3-none-any.whl", hash = "sha256:9158997b8373a80a19809858d052c3321108651c585f83c026cc801b1b34190d", size = 25364, upload-time = "2026-04-21T20:52:50.893Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/2e/905756faf6bada00adccb5dbc9e5987760675b682f08dd1312a40042a838/testresources-2.0.2.tar.gz", hash = "sha256:2cbf3d7e00ab2e9fe24b754a102644f6f334244980464c38233b18127f1deaec", size = 45057, upload-time = "2025-04-22T10:24:29.8Z" } [[package]] name = "testscenarios" -version = "0.5.0" +version = "0.6.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pbr" }, - { name = "testtools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f0/de/b0b5b98c0f38fd7086d082c47fcb455eedd39a044abe7c595f5f40cd6eed/testscenarios-0.5.0.tar.gz", hash = "sha256:c257cb6b90ea7e6f8fef3158121d430543412c9a87df30b5dde6ec8b9b57a2b6", size = 20951, upload-time = "2015-05-04T01:37:16.108Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/47750833ccb3bfd72f6082a644e0ea41fc0c54a6f5a0e98f73265af096eb/testscenarios-0.6.2.tar.gz", hash = "sha256:ab5ae8cd550e11ea978151981e7a8ecac329b7b22e4dec706b1e6fe213f463e7", size = 15698, upload-time = "2026-05-22T11:46:06.934Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/25/2f10da0d5427989fefa5ab51e697bc02625bbb7de2be3bc8452462efac78/testscenarios-0.5.0-py2.py3-none-any.whl", hash = "sha256:480263fa5d6e618125bdf092aab129a3aeed5996b1e668428f12cc56d6d01d28", size = 21002, upload-time = "2015-05-04T01:37:23.545Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/af3b3139963665af3bb6771c0f2762d0cd2726c5264ef56b3422853cfebb/testscenarios-0.6.2-py3-none-any.whl", hash = "sha256:54626a5aa3cd0cae2f5b689de2f1bac73376c926b1717c456d4d483f699ea4ba", size = 12187, upload-time = "2026-05-22T11:46:05.706Z" }, ] [[package]] name = "testtools" -version = "2.8.0" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/3c/e891c4b6c1824e1adf5b08ee1057925254226c42c83995780acdb67cd3ee/testtools-2.8.0.tar.gz", hash = "sha256:b4496f147a7b79440242496889a734179795df345b57d5aa406a09c90572dba1", size = 201854, upload-time = "2025-11-10T10:24:55.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/80/af955640b4e8348aaa8026f9f48304811ec3342d9928ddb611d38a190ea0/testtools-2.9.1.tar.gz", hash = "sha256:39ad9eb9e1b935d6838f4b3aee4d6e72db65df5602f6feda998dbb4fe8de0b19", size = 221377, upload-time = "2026-04-24T09:42:51.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/28/8e5564487da81d79191e7a73659b43a781ba12f1be81a0534c7e3939cedd/testtools-2.8.0-py3-none-any.whl", hash = "sha256:4371613e430ae5fbafcfc1396321c16c84cd9d4fec6d1dc5756ee07e097af7ac", size = 180244, upload-time = "2025-11-10T10:24:50.222Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5f/80ea589bc04651ea10c0f53da06cb0ac33b616b0ec78bf7dc8d8fd642f5d/testtools-2.9.1-py3-none-any.whl", hash = "sha256:faf2d689b3614d87459f7f715fc8349a3f77a252486b104d0cfe635b44326895", size = 110306, upload-time = "2026-04-24T09:42:50.102Z" }, ] [[package]] name = "tooz" -version = "7.0.0" +version = "9.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "debtcollector" }, { name = "fasteners" }, { name = "futurist" }, { name = "msgpack" }, @@ -1992,36 +2011,36 @@ dependencies = [ { name = "tenacity" }, { name = "voluptuous" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/80/79441bdc556221f1491c2d4855d0170de170730a21d067d4edb2cbbd3678/tooz-7.0.0.tar.gz", hash = "sha256:af0aa21cb8b7bd561df3aea85b127e54858975314ecb69d1eac56a03e6e5b8d5", size = 102569, upload-time = "2025-06-05T13:24:30.968Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/8f/5598ffd345fc6899ad8e1dc4c8ca40310796cb3de3d33fcc6435c207ddb8/tooz-9.0.1.tar.gz", hash = "sha256:2c332f668ad910b46b54e7f1db98166a0a79ce8c4d8148206d503c4517d4ceb0", size = 105717, upload-time = "2026-07-30T17:10:09.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/2b/3117ab1da7742429ad4548e9abbb8e1d2819fde6a9c63c6d53863ec38c81/tooz-7.0.0-py3-none-any.whl", hash = "sha256:023b8692dd151984558a409151b3b97d3969fe4346e905b6ce58f408789fc62d", size = 93305, upload-time = "2025-06-05T13:24:28.434Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/dcc2bb5b2810a910bdbc5d5d0d66ccaa5878947efc4b53e03ce0fd78f80f/tooz-9.0.1-py3-none-any.whl", hash = "sha256:4eb79a63bf7ac3bd7caab3a96d03bd135d1d14f8bcc3913f358ac24d03a648fe", size = 90596, upload-time = "2026-07-30T17:10:08.27Z" }, ] [[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]] name = "tzdata" -version = "2025.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] name = "urllib3" -version = "1.26.20" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] @@ -2035,65 +2054,96 @@ wheels = [ [[package]] name = "voluptuous" -version = "0.15.2" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/af/a54ce0fb6f1d867e0b9f0efe5f082a691f51ccf705188fca67a3ecefd7f4/voluptuous-0.15.2.tar.gz", hash = "sha256:6ffcab32c4d3230b4d2af3a577c87e1908a714a11f6f95570456b1849b0279aa", size = 51651, upload-time = "2024-07-02T19:10:00.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/f4/0738e6849858deae22218be3bbb8207ba83a96e9d0ec7e8e8cd67b30e5ca/voluptuous-0.16.0.tar.gz", hash = "sha256:006535e22fed944aec17bef6e8725472476194743c87bd233e912eb463f8ff05", size = 54238, upload-time = "2025-12-18T23:18:46.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/a8/8f9cc6749331186e6a513bfe3745454f81d25f6e34c6024f88f80c71ed28/voluptuous-0.15.2-py3-none-any.whl", hash = "sha256:016348bc7788a9af9520b1764ebd4de0df41fe2138ebe9e06fa036bf86a65566", size = 31349, upload-time = "2024-07-02T19:09:58.125Z" }, + { url = "https://files.pythonhosted.org/packages/d1/00/0e0da784245c93cf346150ab67634177bf277f93b7a162bb56c928c39c04/voluptuous-0.16.0-py3-none-any.whl", hash = "sha256:ee342095263e1b5afbd4d418cb5adc92810eebfd07696bb033a261210df33db4", size = 31931, upload-time = "2025-12-18T23:18:44.694Z" }, +] + +[[package]] +name = "waitress" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/cb/04ddb054f45faa306a230769e868c28b8065ea196891f09004ebace5b184/waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f", size = 179901, upload-time = "2024-11-16T20:02:35.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, ] [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.8.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, ] [[package]] name = "webob" -version = "1.8.9" +version = "1.8.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/0b/1732085540b01f65e4e7999e15864fe14cd18b12a95731a43fd6fd11b26a/webob-1.8.9.tar.gz", hash = "sha256:ad6078e2edb6766d1334ec3dee072ac6a7f95b1e32ce10def8ff7f0f02d56589", size = 279775, upload-time = "2024-10-24T03:19:20.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/92/9329872e36be90e25ba616ac6e9ee8c1be4326a1ea3fdffcdabd0eb48efb/webob-1.8.11.tar.gz", hash = "sha256:aa8c27231070b135c025e567a9cd7eda03f4df71352ffaac740cb6a75f0f81a5", size = 286047, upload-time = "2026-08-02T06:26:26.063Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/bd/c336448be43d40be28e71f2e0f3caf7ccb28e2755c58f4c02c065bfe3e8e/WebOb-1.8.9-py2.py3-none-any.whl", hash = "sha256:45e34c58ed0c7e2ecd238ffd34432487ff13d9ad459ddfd77895e67abba7c1f9", size = 115364, upload-time = "2024-10-24T03:19:18.642Z" }, + { url = "https://files.pythonhosted.org/packages/c1/71/370a65bc7726a1cbf478445c898c7affe907aa42e2e9e5bb38319db74115/webob-1.8.11-py2.py3-none-any.whl", hash = "sha256:4addd1d38d6a7fbe0eda22d45f25a40d74c8b290f3a99c0ac3d4023cf21f2da2", size = 118319, upload-time = "2026-08-02T06:26:24.765Z" }, +] + +[[package]] +name = "webtest" +version = "3.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "waitress" }, + { name = "webob" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/85/73877efb10ba4ef02364d5671724b1326d482dc928eace5fe114215443b7/webtest-3.0.7.tar.gz", hash = "sha256:7aeab50f970d46c068e7a36dd162cb242591edf72a1d04efd21374772b931741", size = 80260, upload-time = "2025-10-06T20:38:59.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/74/cb337bb1758254589b6fdc0aeeb91fa987ac1e6877a79b75edbd214fc0a9/webtest-3.0.7-py3-none-any.whl", hash = "sha256:2f51a0844f3a8beaef89bc23d225fe05ad816f7e429ffcc655a13013a799ac6c", size = 32393, upload-time = "2025-10-06T20:38:57.728Z" }, +] + +[[package]] +name = "win-inet-pton" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/da/0b1487b5835497dea00b00d87c2aca168bb9ca2e2096981690239e23760a/win_inet_pton-1.1.0.tar.gz", hash = "sha256:dd03d942c0d3e2b1cf8bab511844546dfa5f74cb61b241699fa379ad707dea4f", size = 2949, upload-time = "2019-02-19T17:46:23.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/31/ff772a44aa56319df8afbb0b34f1a856f66f05b9d5f1fed917849e47fdae/win_inet_pton-1.1.0-py2.py3-none-any.whl", hash = "sha256:eaf0193cbe7152ac313598a0da7313fb479f769343c0c16c5308f64887dc885b", size = 4848, upload-time = "2019-02-19T17:46:22.182Z" }, ] [[package]] name = "wrapt" -version = "2.0.1" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b0/c1f5a970721f06b85c0cd5142e0ff8fe067708abd779b0c4f4be7d61d09f/wrapt-2.3.0.tar.gz", hash = "sha256:681a2d0eefd721998f90642762b8e75c2159ec531b20ad5e437245ea7b06a107", size = 131509, upload-time = "2026-07-28T06:06:14.895Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/73/8cb252858dc8254baa0ce58ce382858e3a1cf616acebc497cb13374c95c6/wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c", size = 78129, upload-time = "2025-11-07T00:43:48.852Z" }, - { url = "https://files.pythonhosted.org/packages/19/42/44a0db2108526ee6e17a5ab72478061158f34b08b793df251d9fbb9a7eb4/wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841", size = 61205, upload-time = "2025-11-07T00:43:50.402Z" }, - { url = "https://files.pythonhosted.org/packages/4d/8a/5b4b1e44b791c22046e90d9b175f9a7581a8cc7a0debbb930f81e6ae8e25/wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62", size = 61692, upload-time = "2025-11-07T00:43:51.678Z" }, - { url = "https://files.pythonhosted.org/packages/11/53/3e794346c39f462bcf1f58ac0487ff9bdad02f9b6d5ee2dc84c72e0243b2/wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf", size = 121492, upload-time = "2025-11-07T00:43:55.017Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7e/10b7b0e8841e684c8ca76b462a9091c45d62e8f2de9c4b1390b690eadf16/wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9", size = 123064, upload-time = "2025-11-07T00:43:56.323Z" }, - { url = "https://files.pythonhosted.org/packages/0e/d1/3c1e4321fc2f5ee7fd866b2d822aa89b84495f28676fd976c47327c5b6aa/wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b", size = 117403, upload-time = "2025-11-07T00:43:53.258Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b0/d2f0a413cf201c8c2466de08414a15420a25aa83f53e647b7255cc2fab5d/wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba", size = 121500, upload-time = "2025-11-07T00:43:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/bd/45/bddb11d28ca39970a41ed48a26d210505120f925918592283369219f83cc/wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684", size = 116299, upload-time = "2025-11-07T00:43:58.877Z" }, - { url = "https://files.pythonhosted.org/packages/81/af/34ba6dd570ef7a534e7eec0c25e2615c355602c52aba59413411c025a0cb/wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb", size = 120622, upload-time = "2025-11-07T00:43:59.962Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/693a13b4146646fb03254636f8bafd20c621955d27d65b15de07ab886187/wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9", size = 58246, upload-time = "2025-11-07T00:44:03.169Z" }, - { url = "https://files.pythonhosted.org/packages/a7/36/715ec5076f925a6be95f37917b66ebbeaa1372d1862c2ccd7a751574b068/wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75", size = 60492, upload-time = "2025-11-07T00:44:01.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/3e/62451cd7d80f65cc125f2b426b25fbb6c514bf6f7011a0c3904fc8c8df90/wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b", size = 58987, upload-time = "2025-11-07T00:44:02.095Z" }, - { url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4a/d17a0fad1bf1c5f2c887ff71fef75654141b0880bff71d157d955b5bec3a/wrapt-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a45ffae742ce91a16e11cb6c7cd71e7f9994f3cbd283b962ab093f5c6dcf525", size = 82139, upload-time = "2026-07-28T06:04:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/51b92daaf6defb57f4dc56bdcce985400f75c6984a03ca5e78ccac717028/wrapt-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69e477046f2237ef0bc6547544ee73008dc764ca26eff44f09e976d221b34d5d", size = 82723, upload-time = "2026-07-28T06:04:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/28/7f/cfd9bc4b1f5e424eeea83d0493e43f3b1b02707ce8e50c47945873982bd5/wrapt-2.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d221a6e6ddd302b8397433184e96b59f259f50024b854db1c411a881586b6b8", size = 172381, upload-time = "2026-07-28T06:04:37.674Z" }, + { url = "https://files.pythonhosted.org/packages/cb/89/ff7814f6eb6856b479946117d1138a2fbb46cdb6b1f379db359056c69743/wrapt-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:392158c9a7f2ab1b8699418bfc0fe6f83548788c418b27d7bf2019ad3405cebb", size = 174120, upload-time = "2026-07-28T06:04:38.987Z" }, + { url = "https://files.pythonhosted.org/packages/12/1e/8eded8615d39e3ce81f626937a3a87b280a2a86239a2bf14a4b4bb345034/wrapt-2.3.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e5301c35cf75655eb33498f2bd6ae8703ca19940e3167dc9cdf740c712a39c60", size = 163035, upload-time = "2026-07-28T06:04:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/35/ea/a0af2d9da62897af2a055484920de05dade30d2ba2c0d65cbdea875d3d8b/wrapt-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:418f54bb09d1762db02c7009b4051149893af3153a87f92d70356703c11eea02", size = 171887, upload-time = "2026-07-28T06:04:41.614Z" }, + { url = "https://files.pythonhosted.org/packages/7e/dd/63cd4c864c65ef4906df64bd2d378f4a62b54f28063f282dfb3bf93caead/wrapt-2.3.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1598becd30f8f2777d18564064eb4f4dbe1ab0e05a8f09786d0ef505ac782bf3", size = 161113, upload-time = "2026-07-28T06:04:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/82f1fc9e431b5c2c5a6d201aa865dbeae3984c311c6d11a185f0c8367cf6/wrapt-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3da470536bf9645143323dd41b32db55c6f4304ad382094c1a1da8a92061e10d", size = 170530, upload-time = "2026-07-28T06:04:44.212Z" }, + { url = "https://files.pythonhosted.org/packages/37/a5/5dc590e863a419930d988f8b7ca3e75a6befcfb10b6003b3a152f3d5f732/wrapt-2.3.0-cp312-cp312-win32.whl", hash = "sha256:fb8e2e6704a1e0b1b989546c69e2688371ef4a07fa5f61bde3eb6211186f5ac1", size = 78323, upload-time = "2026-07-28T06:04:45.484Z" }, + { url = "https://files.pythonhosted.org/packages/51/f9/4a6925a07951df56394f7e6ebe14f69f1c5ef9d87aa63e0839acf15aa63a/wrapt-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdc021cb0b62471d6aac7f2bd92f3b4658073775f9ee7fcd325c511129e7bcc8", size = 81180, upload-time = "2026-07-28T06:04:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4f/8b5de0395b2a72216751d41c9861df6facaeb611b619d8810ed2b3b23eb2/wrapt-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:67bfe2485f50368c3fcd2275fc1fd100e350d601e0058921a7c82678a465aeab", size = 80155, upload-time = "2026-07-28T06:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/00/39/3daf9f47be208606586de4568ba6713db53ebc8fd7a575aea1fe57983b69/wrapt-2.3.0-py3-none-any.whl", hash = "sha256:d8c7ed08477429752b8c44991f40ad7838b18332a160698740a6bfbc10d998a2", size = 61866, upload-time = "2026-07-28T06:06:12.9Z" }, ] [[package]] name = "yappi" -version = "1.7.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/76/264b149c6a11bb9094336c87d904953d4ba05eb0a4172e7e2d6523285a48/yappi-1.7.3.tar.gz", hash = "sha256:bef71ad0595b600261668dcb1e18b935a7117a724c04d7be60d9d246e32d0928", size = 59594, upload-time = "2025-10-24T11:17:20.156Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/ea/a4bdae99d0463dff28c53b07f9a98333064e28335a0513ad26c9f8b7464a/yappi-1.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:835ba8b6078aa480c185c43e70489d6f7730d3c3570a51451d1f9e29f0fdecf5", size = 32965, upload-time = "2025-10-24T11:16:36.217Z" }, - { url = "https://files.pythonhosted.org/packages/de/9e/ac369cf4b42676b24684cd7bb16a439b76a46d8d1fe5abe13f88c5dabe48/yappi-1.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a23419753961c523a4d98799e199b32e7102bc1b4bd2279199e63e9bc37c0874", size = 32901, upload-time = "2025-10-24T11:16:37.042Z" }, - { url = "https://files.pythonhosted.org/packages/e3/59/f85c08afbc3e0c344d27a00978438a15f384bf1f0bfc1761df66e5141edd/yappi-1.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b26c062c072ee1831d77cf06f8a812dc0cbcac63ef2f5b4cc6b4e67718603b6", size = 81700, upload-time = "2025-10-24T11:16:37.831Z" }, - { url = "https://files.pythonhosted.org/packages/76/ff/eb0a78fbea9aff0e5aa49932cc471381dd3bf9be500893d3fd72d17bf132/yappi-1.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54d17fb356f2cfbb4862901d05cf686bddd46d5e5d3b4bbcb3a0ead3d4fac77d", size = 81291, upload-time = "2025-10-24T11:16:39.083Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/6df059b9c0d9932561e4a6e1e7495cc97a909895d0278b034eddbad1da62/yappi-1.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c62600783a0cf659c46324264996489fc6348dfc113faaf31b7c9af8e747133", size = 79029, upload-time = "2025-10-24T11:16:39.953Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ab/e2787c5d1261e08eec6682b76ba39d4a0ace5fa4e778f8255e82c6a7d4e0/yappi-1.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cb41e4243907368d24081a4acd45d9dfa0867c8cb372491bb00d774f03fb21f", size = 79163, upload-time = "2025-10-24T11:16:41.203Z" }, - { url = "https://files.pythonhosted.org/packages/51/c0/ffb170e9e648c9d623648427e738a8d42ad708c7f2ad4413d84533bdfa7e/yappi-1.7.3-cp312-cp312-win32.whl", hash = "sha256:b93bbf4e6951c1ace9687ac6b027c4b14a581359f233b2dfb21108fe08650397", size = 32608, upload-time = "2025-10-24T11:16:42.11Z" }, - { url = "https://files.pythonhosted.org/packages/04/2c/5cf8208f904644568c1fb74ddd6ed3ed2eccfccb239f0d72386d1cb69c6f/yappi-1.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:396c183dd0ab93532f2fe5c1742b96e136846d56c249b85e72b653af56d11352", size = 34962, upload-time = "2025-10-24T11:16:43.565Z" }, - { url = "https://files.pythonhosted.org/packages/bd/de/f4d21b20c1e6d825977888e1dcf77070709ece92c3e479deb10b55c61e31/yappi-1.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:0317c9bf8908437578cb7661d2d5786043d2817b0377bb1e493669a1f1dbad76", size = 32591, upload-time = "2025-10-24T11:16:44.442Z" }, +version = "1.7.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/47/f7ec7744dff1104560d6276f951a8182f5b805e8d86ece591aebd0512845/yappi-1.7.6.tar.gz", hash = "sha256:c94281936af77c00c6ac2306a0e7f85a67e354d717120df85fcc5dfb9243d4dd", size = 62639, upload-time = "2026-03-17T22:31:40.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/0e/948fdb5980494c50fcf4d24cfd1d5c2ea9c48d4dd151e6b9c032d7ac5fed/yappi-1.7.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56fae31c4e09448a9919c1e6a4f976b2a49aa914f42e6e95355f6329c83003da", size = 33256, upload-time = "2026-03-17T22:30:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/af330ad8ede549ecaa8b29d3a2185e2209b3a87056c05dbaaa7841497c6d/yappi-1.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:15ed0d845e30b35952d09dd4f70df81089db05b735d57c75e0924ebacda14a34", size = 33189, upload-time = "2026-03-17T22:30:58.733Z" }, + { url = "https://files.pythonhosted.org/packages/7a/80/220c5a34e3a4949cea0e0ff1049afab3a67f1bfb348db89a16bb74c77621/yappi-1.7.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e878f63781761db7b62265468d78147b95df9ca2af9bc5140f94ad0faffe11c", size = 82799, upload-time = "2026-03-17T22:30:59.649Z" }, + { url = "https://files.pythonhosted.org/packages/94/e0/e49b8e12140dba82767ee9fd8de1577be90a09a083aa6e0f02fdfc3e8ee9/yappi-1.7.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a88615e2b9817887f6d1addfd12466a8529f25acc58b656205ae3f641cd725b", size = 82322, upload-time = "2026-03-17T22:31:00.524Z" }, + { url = "https://files.pythonhosted.org/packages/1f/95/fdcabc38a3e70e7c6394d07868ff25c9984a1ea97020cc65d8858c71aa62/yappi-1.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:178b23a56a59ddd58a528848e9242040ecad6d2fe0bcfb439455eef02cd43b07", size = 79978, upload-time = "2026-03-17T22:31:01.456Z" }, + { url = "https://files.pythonhosted.org/packages/83/e6/6e11c44a90725a6f1afa4bc308618b2efc07a90d4c06e1d5a3b0125f8e6c/yappi-1.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62cbb47dffca45b906d52a3c8f02e508f67d657275fb9d897e1e736fe5afe25f", size = 80053, upload-time = "2026-03-17T22:31:02.366Z" }, + { url = "https://files.pythonhosted.org/packages/a2/79/f056c72ba190d186fe52eb6a9181aab15dbd68a868ab5c7375e3b5796565/yappi-1.7.6-cp312-cp312-win32.whl", hash = "sha256:dbcf79ee2f1a96ec52e8291c07e27c0e38eead61a5c24d57eb467b5d9e6f2f9f", size = 32906, upload-time = "2026-03-17T22:31:03.29Z" }, + { url = "https://files.pythonhosted.org/packages/0c/89/a9ee11f80482263b7268c8968a4e675393454167b20574831a357610afd4/yappi-1.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:59bd23fb39a7b9027c5eecc94585042849cb36be9af2d35c31812be1408af356", size = 35204, upload-time = "2026-03-17T22:31:04.131Z" }, + { url = "https://files.pythonhosted.org/packages/3b/72/711a33a5bf45e5af484a48001b75908dade76d4b8ed5d180f89dc62c5a5a/yappi-1.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:0adc831b099a554831335819d7eb1643e189e4f3aa2db873ca5d584bd42dba01", size = 32836, upload-time = "2026-03-17T22:31:05.116Z" }, ] diff --git a/scripts/scenario-report.py b/scripts/scenario-report.py new file mode 100644 index 000000000..d5da84c5e --- /dev/null +++ b/scripts/scenario-report.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""Render a scenario traceability matrix from a scenario catalog + JUnit XML. + +Reads a SCENARIOS.md catalog (``### <ID> — <title>`` headings) and, optionally, +a JUnit XML file produced by ``pytest --junitxml`` where each scenario test was +tagged with a ``scenario`` property (see the scenarios conftest.py). Emits a +markdown table mapping each catalogued scenario to its implementing test(s) and +last-run status, suitable for ``$GITHUB_STEP_SUMMARY`` or a docs page. + +Reporting only: always exits 0. The catalog<->marker equivalence is enforced +separately by test_scenario_coverage.py. +""" + +import argparse +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + +# Catalog heading parsing is shared with the coverage meta-test so the format is +# defined once. Available because the script runs in the neutron-understack env. +from neutron_understack.tests.scenarios.catalog import parse_catalog as _parse_text + +_STATUS = { + "pass": "✅ pass", + "fail": "❌ fail", + "xfail": "⚠️ known bug (xfail)", + "xpass": "🎉 fixed — remove xfail", + "skipped": "⏭️ skipped", + "missing": "❓ no test", + "notrun": "· not run", +} + + +def parse_catalog(path): + """Return ordered list of (id, title) from the catalog headings.""" + return _parse_text(Path(path).read_text()) + + +def _testcase_status(testcase): + failure = testcase.find("failure") + if failure is not None or testcase.find("error") is not None: + # A strict xfail that unexpectedly passes is a JUnit <failure> whose + # message is "[XPASS(strict)] ...": the bug is fixed, remove the marker. + if failure is not None and (failure.get("message") or "").startswith("[XPASS"): + return "xpass" + return "fail" + skipped = testcase.find("skipped") + if skipped is not None: + return "xfail" if (skipped.get("type") or "").endswith("xfail") else "skipped" + return "pass" + + +def parse_junit(path): + """Return {scenario_id: [(test_name, status), ...]} from JUnit XML.""" + results = {} + # Input is our own pytest JUnit output (trusted, CI-generated), not + # untrusted external XML, so stdlib ElementTree is fine here. + root = ET.parse(path).getroot() # noqa: S314 + for testcase in root.iter("testcase"): + sid = None + for prop in testcase.iter("property"): + if prop.get("name") == "scenario": + sid = prop.get("value") + break + if sid is None: + continue + results.setdefault(sid, []).append( + (testcase.get("name", "?"), _testcase_status(testcase)) + ) + return results + + +def render(catalog, results, junit_provided): + lines = [ + "## ML2 scenario traceability", + "", + "| Scenario | Title | Test | Status |", + "| --- | --- | --- | --- |", + ] + for sid, title in catalog: + runs = results.get(sid) + if not runs: + status_key = "missing" if junit_provided else "notrun" + lines.append(f"| `{sid}` | {title} | — | {_STATUS[status_key]} |") + continue + for test_name, status in runs: + lines.append(f"| `{sid}` | {title} | `{test_name}` | {_STATUS[status]} |") + # Surface any scenarios seen in JUnit that are not in the catalog. + catalogued = {sid for sid, _ in catalog} + for sid in sorted(set(results) - catalogued): + for test_name, status in results[sid]: + lines.append( + f"| `{sid}` (uncataloged!) | — | `{test_name}` | {_STATUS[status]} |" + ) + lines.append("") + return "\n".join(lines) + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--catalog", required=True, help="path to SCENARIOS.md") + parser.add_argument("--junit", help="path to pytest JUnit XML (optional)") + args = parser.parse_args(argv) + + catalog = parse_catalog(args.catalog) + results = parse_junit(args.junit) if args.junit else {} + sys.stdout.write(render(catalog, results, junit_provided=bool(args.junit))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())