This is an automated email from the ASF dual-hosted git repository. arm pushed a commit to branch arm in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit db9623046b767c92026c59dfb408dc03819743d6 Author: Alastair McFarlane <[email protected]> AuthorDate: Wed Mar 25 17:51:46 2026 +0000 #901 - add support for XML in sbom tooling and conversion from XML to JSON --- .pre-commit-config.yaml | 4 +- atr/analysis.py | 1 + atr/get/draft.py | 13 ++ atr/models/results.py | 11 ++ atr/models/sql.py | 1 + atr/post/draft.py | 76 +++++++++- atr/sbom/cli.py | 29 +++- atr/sbom/conformance.py | 30 ++-- atr/sbom/constants/licenses.py | 1 + atr/sbom/cyclonedx.py | 3 +- atr/sbom/licenses.py | 22 ++- atr/sbom/models/__init__.py | 4 +- atr/sbom/models/bom.py | 100 ------------- atr/sbom/models/bundle.py | 10 +- atr/sbom/osv.py | 36 ++--- atr/sbom/tool.py | 24 +-- atr/sbom/utilities.py | 60 ++++++-- atr/storage/writers/sbom.py | 27 ++++ atr/tasks/__init__.py | 2 + atr/tasks/sbom.py | 73 ++++++++- atr/templates/draft-tools.html | 8 + pip-audit.requirements | 18 +-- typestubs/cyclonedx/model/bom.pyi | 44 ++++++ typestubs/cyclonedx/model/component.pyi | 48 ++++++ typestubs/cyclonedx/output/__init__.pyi | 22 +++ typestubs/py_serializable/__init__.pyi | 94 ++++++++++++ uv.lock | 252 ++++++++++++++++---------------- 27 files changed, 696 insertions(+), 317 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6d4690b5..a0a67f03 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,7 +61,7 @@ repos: hooks: - id: shellcheck - repo: https://github.com/rtts/djhtml - rev: 3.0.10 + rev: 3.0.11 hooks: - id: djhtml files: .*/.*\.html$ @@ -84,7 +84,7 @@ repos: # - --profile=jinja # - --reformat - repo: https://github.com/thibaudcolas/pre-commit-stylelint - rev: v17.5.0 + rev: v17.6.0 hooks: - id: stylelint additional_dependencies: ['[email protected]', '[email protected]'] diff --git a/atr/analysis.py b/atr/analysis.py index 8ee7d7d2..2ab5ad13 100755 --- a/atr/analysis.py +++ b/atr/analysis.py @@ -134,6 +134,7 @@ SKIPPABLE_SUFFIXES: Final[list[str]] = [ STANDALONE_METADATA_SUFFIXES: Final[frozenset[str]] = frozenset( { ".cdx.json", + ".cdx.xml", } ) diff --git a/atr/get/draft.py b/atr/get/draft.py index 538915db..12d6c70c 100644 --- a/atr/get/draft.py +++ b/atr/get/draft.py @@ -90,6 +90,18 @@ async def tools( submit_classes="btn-outline-secondary", empty=True, ) + sbom_convert_form = form.render( + model_cls=form.Empty, + action=util.as_url( + post.draft.sbomconvert, + project_key=str(project_key), + version_key=str(version_key), + file_path=str(file_path), + ), + submit_label="Convert XML SBOM (.cdx.xml)", + submit_classes="btn-outline-secondary", + empty=True, + ) return await template.render( "draft-tools.html", @@ -102,4 +114,5 @@ async def tools( format_file_size=util.format_file_size, sha512_form=sha512_form, sbom_form=sbom_form, + sbom_convert_form=sbom_convert_form, ) diff --git a/atr/models/results.py b/atr/models/results.py index 99f145f0..cd04e226 100644 --- a/atr/models/results.py +++ b/atr/models/results.py @@ -163,6 +163,16 @@ class SBOMAugment(schema.Strict): ) +class SBOMConvert(schema.Strict): + kind: Literal["sbom_convert"] = schema.Field(alias="kind") + path: str = schema.description("The path to the converted SBOM file") + bom_version: int | None = schema.Field( + default=None, + strict=False, + description="BOM Version produced by the convert task", + ) + + class SBOMQsScore(schema.Strict): kind: Literal["sbom_qs_score"] = schema.Field(alias="kind") project_key: safe.ProjectKey = schema.description("Project name") @@ -241,6 +251,7 @@ Results = Annotated[ | MessageSend | MetadataUpdate | SBOMAugment + | SBOMConvert | SBOMGenerateCycloneDX | SBOMOSVScan | SBOMQsScore diff --git a/atr/models/sql.py b/atr/models/sql.py index 97f34564..09c711fb 100644 --- a/atr/models/sql.py +++ b/atr/models/sql.py @@ -214,6 +214,7 @@ class TaskType(enum.StrEnum): QUARANTINE_VALIDATE = "quarantine_validate" RAT_CHECK = "rat_check" SBOM_AUGMENT = "sbom_augment" + SBOM_CONVERT = "sbom_convert" SBOM_GENERATE_CYCLONEDX = "sbom_generate_cyclonedx" SBOM_OSV_SCAN = "sbom_osv_scan" SBOM_QS_SCORE = "sbom_qs_score" diff --git a/atr/post/draft.py b/atr/post/draft.py index 3666679f..ebfd9ec3 100644 --- a/atr/post/draft.py +++ b/atr/post/draft.py @@ -253,7 +253,7 @@ async def sbomgen( or rel_path.name.endswith(".jar") ): raise base.ASFQuartException( - f"SBOM generation requires .tar.gz, .tgz, .zip or .jar files. Received: {file_path!s}", errorcode=400 + f"SBOM generation requires .tar.gz, .tgz, .zip or .jar files. Received: {rel_path.name}", errorcode=400 ) try: @@ -309,3 +309,77 @@ async def sbomgen( project_key=str(project_key), version_key=str(version_key), ) + + [email protected] +async def sbomconvert( + session: web.Committer, + _draft_sbomconvert: Literal["draft/sbomconvert"], + project_key: safe.ProjectKey, + version_key: safe.VersionKey, + file_path: safe.RelPath, + empty_form: form.Empty, +) -> web.WerkzeugResponse: + """ + URL: /draft/sbomconvert/<project_key>/<version_key>/<file_path> + Convert an XML CycloneDX SBOM file into JSON, creating a new revision. + """ + rel_path = file_path.as_path() + + # Check that the file is a .cdx.xml file before continuing + if not rel_path.name.endswith(".cdx.xml"): + raise base.ASFQuartException(f"SBOM converter requires .cdx.xml file. Received: {rel_path.name}", errorcode=400) + + try: + description = "SBOM conversion through web interface" + async with storage.write(session) as write: + wacp = await write.as_project_committee_participant(project_key) + + async def modify(path: pathlib.Path, old_rev: sql.Revision | None) -> None: + path_in_new_revision = path / rel_path + sbom_path_rel = rel_path.with_suffix(".cdx.json").name + sbom_path_in_new_revision = path / rel_path.parent / sbom_path_rel + + # Check that the source file exists in the new revision + if not await aiofiles.os.path.exists(path_in_new_revision): + log.error(f"Source file {rel_path} not found in new revision for SBOM generation.") + raise web.FlashError("Source artifact file not found in the new revision.") + + # Check that the SBOM file does not already exist in the new revision + if await aiofiles.os.path.exists(sbom_path_in_new_revision): + raise base.ASFQuartException("SBOM file already exists", errorcode=400) + + # This shouldn't happen as we need a revision to kick the task off from + if old_rev is None: + raise web.FlashError("Internal error: Revision not found") + + # Create and queue the task, using paths within the new revision + sbom_task = await wacp.sbom.convert_cyclonedx( + project_key, + version_key, + old_rev.safe_number, + str(path_in_new_revision), + str(sbom_path_in_new_revision), + ) + success = await interaction.wait_for_task(sbom_task) + if not success: + raise web.FlashError("Internal error: SBOM conversion timed out") + + result = await wacp.revision.create_revision_with_quarantine( + project_key, version_key, session.uid, description=description, modify=modify + ) + + except Exception as e: + log.exception("Error generating SBOM:") + await quart.flash(f"Error generating SBOM: {e!s}", "error") + return await session.redirect(get.compose.selected, project_key=str(project_key), version_key=str(version_key)) + + success = f"SBOM generated for {rel_path.name}" + if isinstance(result, sql.Quarantined): + success += ". Archive validation in progress." + return await session.redirect( + get.compose.selected, + success=success, + project_key=str(project_key), + version_key=str(version_key), + ) diff --git a/atr/sbom/cli.py b/atr/sbom/cli.py index a9cbd801..48e66150 100644 --- a/atr/sbom/cli.py +++ b/atr/sbom/cli.py @@ -20,6 +20,10 @@ import pathlib import sys import yyjson +from cyclonedx.model.bom import Bom +from cyclonedx.output import make_outputter +from cyclonedx.schema import OutputFormat +from cyclonedx.schema.schema import SCHEMA_VERSIONS from . import models, osv from .conformance import ntia_2021_issues @@ -57,14 +61,21 @@ def command_merge(bundle: models.bundle.Bundle) -> None: patch_ops = asyncio.run(bundle_to_ntia_patch(bundle)) if patch_ops: patch_data = patch_to_data(patch_ops) - merged = bundle.doc.patch(yyjson.Document(patch_data)) - print(merged.dumps()) + output = bundle.doc.patch(yyjson.Document(patch_data)) + else: + output = bundle.doc + if bundle.source_type == "json": + print(output.dumps()) else: - print(bundle.doc.dumps()) + bom: Bom | None = Bom.from_json(data=output.as_obj) + if bom is None: + print("Could not generate patched Bom") + return + print(make_outputter(bom, OutputFormat.XML, bundle.spec_version).output_as_string(indent=2)) def command_missing(bundle: models.bundle.Bundle) -> None: - _warnings, errors = ntia_2021_issues(bundle.bom) + _warnings, errors = ntia_2021_issues(bundle) for error in errors: print(error) @@ -145,7 +156,7 @@ def command_validate_py(bundle: models.bundle.Bundle) -> None: def command_where(bundle: models.bundle.Bundle) -> None: - _warnings, errors = ntia_2021_issues(bundle.bom) + _warnings, errors = ntia_2021_issues(bundle) for error in errors: match error: case models.conformance.MissingProperty(): @@ -154,11 +165,11 @@ def command_where(bundle: models.bundle.Bundle) -> None: case models.conformance.MissingComponentProperty(): components = bundle.bom.components primary_component = bundle.bom.metadata.component if bundle.bom.metadata else None - if (error.index is not None) and (components is not None): - print(components[error.index].model_dump_json(indent=2)) + if (error.index is not None) and len(components) > 0: + print(components[error.index].as_json(SCHEMA_VERSIONS[bundle.spec_version])) print() elif primary_component is not None: - print(primary_component.model_dump_json(indent=2)) + print(primary_component.as_json(SCHEMA_VERSIONS[bundle.spec_version])) print() @@ -172,6 +183,8 @@ def main() -> None: # noqa: C901 sys.exit(1) path = pathlib.Path(sys.argv[2]) bundle = path_to_bundle(path) + if not bundle: + raise RuntimeError("Could not load bundle") match sys.argv[1]: case "license": command_license(bundle) diff --git a/atr/sbom/conformance.py b/atr/sbom/conformance.py index 96b0f638..7d501f1f 100644 --- a/atr/sbom/conformance.py +++ b/atr/sbom/conformance.py @@ -19,6 +19,7 @@ from __future__ import annotations import datetime import urllib.parse +from typing import TYPE_CHECKING import aiohttp import yyjson @@ -27,6 +28,11 @@ from . import constants, models from .maven import cache_read, cache_write from .utilities import get_pointer +if TYPE_CHECKING: + from cyclonedx.model.component import Component + + from .models.bundle import Bundle + def assemble_component_identifier(doc: yyjson.Document, patch_ops: models.patch.Patch, index: int) -> None: # May be able to derive this from other fields @@ -234,7 +240,7 @@ def assemble_metadata_timestamp(doc: yyjson.Document, patch_ops: models.patch.Pa def ntia_2021_issues( - bom_value: models.bom.Bom, + bundle: Bundle, ) -> tuple[list[models.conformance.Missing], list[models.conformance.Missing]]: # 1. Supplier # ECMA-424 1st edition says that this is the supplier of the primary component @@ -270,13 +276,15 @@ def ntia_2021_issues( warnings: list[models.conformance.Missing] = [] errors: list[models.conformance.Missing] = [] + bom_value = bundle.bom + original_metadata = bundle.doc.get_pointer("/metadata") - if bom_value.metadata is not None: + if bom_value.metadata: if bom_value.metadata.supplier is None: errors.append(models.conformance.MissingProperty(property=models.conformance.Property.METADATA_SUPPLIER)) if bom_value.metadata.component is not None: - if bom_value.metadata.component.name is None: + if not bom_value.metadata.component.name: errors.append( models.conformance.MissingComponentProperty(property=models.conformance.ComponentProperty.NAME) ) @@ -299,19 +307,19 @@ def ntia_2021_issues( else: errors.append(models.conformance.MissingProperty(property=models.conformance.Property.METADATA_COMPONENT)) - if bom_value.metadata.author is None: + if len(bom_value.metadata.authors) < 1: errors.append(models.conformance.MissingProperty(property=models.conformance.Property.METADATA_AUTHOR)) - - if bom_value.metadata.timestamp is None: + if original_metadata.get("timestamp") is None: errors.append(models.conformance.MissingProperty(property=models.conformance.Property.METADATA_TIMESTAMP)) else: errors.append(models.conformance.MissingProperty(property=models.conformance.Property.METADATA)) - for index, component in enumerate(bom_value.components or []): + components: list[Component] = list(bom_value.components) + for index, component in enumerate(components): component_type = component.type component_friendly_name = component.name - if component_type is not None: - component_friendly_name = f"{component_type}: {component_friendly_name}" + if component_type: + component_friendly_name = f"{component_type.value}: {component_friendly_name}" if component.supplier is None: errors.append( models.conformance.MissingComponentProperty( @@ -321,7 +329,7 @@ def ntia_2021_issues( ) ) - if component.name is None: + if not component.name: errors.append( models.conformance.MissingComponentProperty( property=models.conformance.ComponentProperty.NAME, @@ -330,7 +338,7 @@ def ntia_2021_issues( ) ) - if component.version is None: + if not component.version: errors.append( models.conformance.MissingComponentProperty( property=models.conformance.ComponentProperty.VERSION, diff --git a/atr/sbom/constants/licenses.py b/atr/sbom/constants/licenses.py index 299c2521..0e0fcee6 100644 --- a/atr/sbom/constants/licenses.py +++ b/atr/sbom/constants/licenses.py @@ -62,6 +62,7 @@ LICENSES: Final[dict[str, list[str]]] = { "PHP-3.01", "PostgreSQL", "Python-2.0", + "PSF-2.0", "SMLNJ", "TCL", "UPL-1.0", diff --git a/atr/sbom/cyclonedx.py b/atr/sbom/cyclonedx.py index 35459798..8bc8d19b 100644 --- a/atr/sbom/cyclonedx.py +++ b/atr/sbom/cyclonedx.py @@ -23,6 +23,7 @@ from typing import TYPE_CHECKING import cyclonedx.exception import cyclonedx.schema +import cyclonedx.validation import cyclonedx.validation.json from .utilities import get_pointer @@ -39,7 +40,7 @@ def validate_cli(bundle_value: models.bundle.Bundle) -> list[str] | None: "validate", "--fail-on-errors", "--input-format", - "json", + bundle_value.source_type, "--input-file", bundle_value.path.as_posix(), ] diff --git a/atr/sbom/licenses.py b/atr/sbom/licenses.py index 470571c8..dd8ee692 100644 --- a/atr/sbom/licenses.py +++ b/atr/sbom/licenses.py @@ -17,19 +17,27 @@ from __future__ import annotations +from typing import TYPE_CHECKING + +from cyclonedx.model.component import Component +from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression + +if TYPE_CHECKING: + from cyclonedx.model.bom import Bom + from . import constants, models from .spdx import license_expression_atoms def check( - bom_value: models.bom.Bom, + bom_value: Bom, include_all: bool = False, ) -> tuple[list[models.licenses.Issue], list[models.licenses.Issue], list[models.licenses.Issue]]: warnings: list[models.licenses.Issue] = [] errors: list[models.licenses.Issue] = [] good: list[models.licenses.Issue] = [] - components = bom_value.components or [] + components: list[Component] = list(bom_value.components) if bom_value.metadata and bom_value.metadata.component: components = [bom_value.metadata.component, *components] @@ -45,16 +53,16 @@ def check( for license_choice in component.licenses: license_expr = None - if license_choice.expression: - license_expr = license_choice.expression - elif license_choice.license and license_choice.license.id: - license_expr = license_choice.license.id + if isinstance(license_choice, LicenseExpression): + license_expr = license_choice.value + elif isinstance(license_choice, DisjunctiveLicense): + license_expr = license_choice.id if not license_expr: continue parse_failed = False - if license_choice.expression: + if isinstance(license_choice, LicenseExpression): try: atoms = license_expression_atoms(license_expr) except ValueError: diff --git a/atr/sbom/models/__init__.py b/atr/sbom/models/__init__.py index 15cf734c..5735eb71 100644 --- a/atr/sbom/models/__init__.py +++ b/atr/sbom/models/__init__.py @@ -17,6 +17,6 @@ from __future__ import annotations -from . import base, bom, bundle, conformance, licenses, osv, patch, sbomqs, tool +from . import base, bundle, conformance, licenses, osv, patch, sbomqs, tool -__all__ = ["base", "bom", "bundle", "conformance", "licenses", "osv", "patch", "sbomqs", "tool"] +__all__ = ["base", "bundle", "conformance", "licenses", "osv", "patch", "sbomqs", "tool"] diff --git a/atr/sbom/models/bom.py b/atr/sbom/models/bom.py deleted file mode 100644 index e02dff26..00000000 --- a/atr/sbom/models/bom.py +++ /dev/null @@ -1,100 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -from __future__ import annotations - -import pydantic - -from .base import Lax - - -class Swid(Lax): - tag_id: str | None = pydantic.Field(default=None, alias="tagId") - - -class Supplier(Lax): - name: str | None = None - url: list[str] | None = None - - -class License(Lax): - id: str | None = None - name: str | None = None - url: str | None = None - - -class LicenseChoice(Lax): - license: License | None = None - expression: str | None = None - - -class Component(Lax): - bom_ref: str | None = pydantic.Field(default=None, alias="bom-ref") - name: str | None = None - version: str | None = None - supplier: Supplier | None = None - purl: str | None = None - cpe: str | None = None - swid: Swid | None = None - licenses: list[LicenseChoice] | None = None - scope: str | None = None - type: str | None = None - - -class ToolComponent(Lax): - name: str | None = None - version: str | None = None - description: str | None = None - supplier: Supplier | None = None - - -class ServiceComponent(Lax): - name: str | None = None - version: str | None = None - description: str | None = None - supplier: Supplier | None = None - authenticated: bool | None = None - - -class Tool(Lax): - name: str | None = None - version: str | None = None - description: str | None = None - - -class Tools(Lax): - components: list[ToolComponent] | None = None - services: list[ServiceComponent] | None = None - - -class Metadata(Lax): - author: str | None = None - timestamp: str | None = None - supplier: Supplier | None = None - component: Component | None = None - tools: Tools | list[Tool] | None = None - - -class Dependency(Lax): - ref: str - depends_on: list[str] | None = pydantic.Field(default=None, alias="dependsOn") - - -class Bom(Lax): - metadata: Metadata | None = None - components: list[Component] | None = None - dependencies: list[Dependency] | None = None diff --git a/atr/sbom/models/bundle.py b/atr/sbom/models/bundle.py index 800e70e0..5ab3d83f 100644 --- a/atr/sbom/models/bundle.py +++ b/atr/sbom/models/bundle.py @@ -18,19 +18,21 @@ from __future__ import annotations import dataclasses -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal if TYPE_CHECKING: import pathlib import yyjson - - from .bom import Bom + from cyclonedx.model.bom import Bom + from cyclonedx.schema import SchemaVersion @dataclasses.dataclass class Bundle: - doc: yyjson.Document + source_type: Literal["json", "xml"] + spec_version: SchemaVersion bom: Bom + doc: yyjson.Document path: pathlib.Path text: str diff --git a/atr/sbom/osv.py b/atr/sbom/osv.py index 227c7816..a8f658f9 100644 --- a/atr/sbom/osv.py +++ b/atr/sbom/osv.py @@ -20,6 +20,8 @@ from __future__ import annotations import os from typing import TYPE_CHECKING, Any +from packageurl import PackageURL + import atr.util as util from . import models @@ -28,6 +30,7 @@ from .utilities import get_pointer, osv_severity_to_cdx if TYPE_CHECKING: import aiohttp import yyjson + from cyclonedx.model.component import Component _DEBUG: bool = os.environ.get("DEBUG_SBOM_TOOL") == "1" _OSV_API_BASE: str = "https://api.osv.dev/v1" @@ -82,7 +85,7 @@ _SOURCE_DATABASE_NAMES = { async def scan_bundle(bundle: models.bundle.Bundle) -> tuple[list[models.osv.ComponentVulnerabilities], list[str]]: - components = bundle.bom.components or [] + components = list(bundle.bom.components) queries, ignored = _scan_bundle_build_queries(components) if _DEBUG: print(f"[DEBUG] Scanning {len(queries)} components for vulnerabilities") @@ -165,27 +168,24 @@ def _assemble_vulnerabilities(doc: yyjson.Document, patch_ops: models.patch.Patc ) -def _component_purl_with_version(component: models.bom.Component) -> str | None: +def _component_purl_with_version(component: Component) -> str | None: + # If we don't know the purl, we can't help if component.purl is None: return None + # If the component purl includes version information, return it + if component.purl.version is not None: + return str(component.purl) + # If not, and we don't have a component version, we still can't add anything if component.version is None: return None version = component.version.strip() if not version: return None - purl = component.purl - split_index = len(purl) - question_index = purl.find("?") - if (question_index != -1) and (question_index < split_index): - split_index = question_index - hash_index = purl.find("#") - if (hash_index != -1) and (hash_index < split_index): - split_index = hash_index - if "@" in purl[:split_index]: - return purl - base = purl[:split_index] - suffix = purl[split_index:] - return f"{base}@{version}{suffix}" + # Clone the purl so we don't affect the component definition + purl = str(component.purl) + new_purl = PackageURL.from_string(purl) + new_purl.version = version + return str(new_purl) async def _fetch_vulnerabilities_for_batch( @@ -256,7 +256,7 @@ async def _paginate_query( def _scan_bundle_build_queries( - components: list[models.bom.Component], + components: list[Component], ) -> tuple[list[tuple[str, dict[str, Any]]], list[str]]: queries: list[tuple[str, dict[str, Any]]] = [] ignored = [] @@ -266,8 +266,8 @@ def _scan_bundle_build_queries( ignored.append(component.name) continue query = {"package": {"purl": purl_with_version}} - if component.bom_ref is not None: - queries.append((component.bom_ref, query)) + if component.bom_ref: + queries.append((str(component.bom_ref), query)) return queries, ignored diff --git a/atr/sbom/tool.py b/atr/sbom/tool.py index 08e98802..4ca1447b 100644 --- a/atr/sbom/tool.py +++ b/atr/sbom/tool.py @@ -17,7 +17,6 @@ from __future__ import annotations -import datetime from typing import TYPE_CHECKING, Any, Final import semver @@ -27,6 +26,8 @@ from . import maven, models, utilities if TYPE_CHECKING: from collections.abc import Callable + from cyclonedx.model.bom import Bom, BomMetaData + _KNOWN_TOOLS: Final[dict[str, models.tool.Tool]] = { # name in file: ( canonical name, friendly name, version callable ) @@ -59,22 +60,17 @@ def outdated_version_core( return None -def plugin_outdated_version(bom_value: models.bom.Bom) -> list[models.tool.Outdated] | None: - if bom_value.metadata is None: +def plugin_outdated_version(bom_value: Bom) -> list[models.tool.Outdated] | None: + if _metadata_is_empty(bom_value.metadata): return [models.tool.OutdatedMissingMetadata()] - timestamp = bom_value.metadata.timestamp - if timestamp is None: - # This quite often isn't available - # We could use the file mtime, but that's extremely heuristic - # return OutdatedMissingTimestamp() - timestamp = datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ") + timestamp = bom_value.metadata.timestamp.strftime("%Y-%m-%dT%H:%M:%SZ") tools: list[Any] = [] tools_value = bom_value.metadata.tools if isinstance(tools_value, list): tools = tools_value elif tools_value: - tools = tools_value.components or [] - services = tools_value.services or [] + tools = list(tools_value.components) + services = list(tools_value.services) tools.extend(services) errors = [] for tool in tools: @@ -102,3 +98,9 @@ def version_parse(version_str: str) -> semver.VersionInfo | None: return semver.VersionInfo.parse(version_str.lstrip("v")) except ValueError: return None + + +def _metadata_is_empty(metadata: BomMetaData) -> bool: + if metadata.component is None and metadata.supplier is None: + return True + return False diff --git a/atr/sbom/utilities.py b/atr/sbom/utilities.py index 5d4e94a7..0227a2b5 100644 --- a/atr/sbom/utilities.py +++ b/atr/sbom/utilities.py @@ -17,14 +17,21 @@ from __future__ import annotations +import json import re -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal import cvss +from cyclonedx.model.bom import Bom +from cyclonedx.output import BaseOutput, make_outputter +from cyclonedx.schema import OutputFormat, SchemaVersion +from defusedxml import ElementTree if TYPE_CHECKING: import pathlib + from cyclonedx.model import Property + import atr.sbom.models.osv as osv import yyjson @@ -74,7 +81,7 @@ def apply_patch( async def bundle_to_ntia_patch(bundle_value: models.bundle.Bundle) -> models.patch.Patch: from .conformance import ntia_2021_issues, ntia_2021_patch - _warnings, errors = ntia_2021_issues(bundle_value.bom) + _warnings, errors = ntia_2021_issues(bundle_value) async with util.create_secure_session() as session: patch_ops = await ntia_2021_patch(session, bundle_value.doc, errors) return patch_ops @@ -89,6 +96,12 @@ async def bundle_to_vuln_patch( return patch_ops +def bundle_outputter(bundle_value: models.bundle.Bundle) -> BaseOutput: + return make_outputter( + bom=bundle_value.bom, output_format=OutputFormat.JSON, schema_version=bundle_value.spec_version + ) + + def cdx_severity_to_osv(severity: list[dict[str, str | float]]) -> tuple[str | None, list[dict[str, str]]]: severities = [ { @@ -111,14 +124,10 @@ def get_pointer(doc: yyjson.Document, path: str) -> Any | None: raise -def get_props_from_bundle(bundle_value: models.bundle.Bundle) -> tuple[int, list[dict[str, str]]]: - version: int | None = get_pointer(bundle_value.doc, "/version") - if version is None: - version = 0 - properties: list[dict[str, str]] | None = get_pointer(bundle_value.doc, "/properties") - if properties is None: - return version, [] - return version, [p for p in properties if "asf:atr:" in p.get("name", "")] +def get_props_from_bundle(bundle_value: models.bundle.Bundle) -> tuple[int, list[Property]]: + version: int = bundle_value.bom.version + properties = bundle_value.bom.properties + return version, [p for p in properties if "asf:atr:" in p.name] def osv_severity_to_cdx(severity: list[dict[str, Any]] | None, textual: str) -> list[dict[str, str | float]] | None: @@ -140,8 +149,35 @@ def patch_to_data(patch_ops: models.patch.Patch) -> list[dict[str, Any]]: def path_to_bundle(path: pathlib.Path) -> models.bundle.Bundle: text = path.read_text(encoding="utf-8") - bom = models.bom.Bom.model_validate_json(text) - return models.bundle.Bundle(doc=yyjson.Document(text), bom=bom, path=path, text=text) + bom: Bom | None = None + source_type: Literal["json", "xml"] | None = None + # Default to latest schema version + spec_version: SchemaVersion | None = None + if path.name.endswith(".json"): + bom_json: dict[str, Any] = json.loads(text) + bom = Bom.from_json(data=bom_json) + source_type = "json" + version_str = bom_json.get("specVersion", "1.7") + spec_version = SchemaVersion.from_version(version_str) + elif path.name.endswith(".xml"): + bom_xml = ElementTree.fromstring(text) + bom = Bom.from_xml(bom_xml) + tag = re.match(r"\{http://cyclonedx.org/schema/bom/(.+)}", bom_xml.tag) + version = "1.7" + if tag: + version = tag.group(1) + spec_version = SchemaVersion.from_version(version) + source_type = "xml" + if bom: + outputter: BaseOutput = make_outputter( + bom=bom, output_format=OutputFormat.JSON, schema_version=spec_version + ) + text: str = outputter.output_as_string() + if not bom or not source_type or not spec_version: + raise ValueError("Error importing BOM") + return models.bundle.Bundle( + doc=yyjson.Document(text), bom=bom, path=path, text=text, source_type=source_type, spec_version=spec_version + ) def _extract_cdx_score(type: str, score_str: str) -> dict[str, str | float]: diff --git a/atr/storage/writers/sbom.py b/atr/storage/writers/sbom.py index 326a0d0b..f204d824 100644 --- a/atr/storage/writers/sbom.py +++ b/atr/storage/writers/sbom.py @@ -105,6 +105,33 @@ class CommitteeParticipant(FoundationCommitter): await self.__data.refresh(sbom_task) return sbom_task + async def convert_cyclonedx( + self, + project_key: safe.ProjectKey, + version_key: safe.VersionKey, + revision_number: safe.RevisionNumber, + file_path: str, + sbom_path: str, + ) -> sql.Task: + sbom_task = sql.Task( + task_type=sql.TaskType.SBOM_CONVERT, + task_args=sbom.ConvertCycloneDX( + artifact_path=file_path, + revision=revision_number, + output_path=sbom_path, + ).model_dump(), + asf_uid=util.unwrap(self.__asf_uid), + added=datetime.datetime.now(datetime.UTC), + status=sql.TaskStatus.QUEUED, + project_key=str(project_key), + version_key=str(version_key), + revision_number=str(revision_number), + ) + self.__data.add(sbom_task) + await self.__data.commit() + await self.__data.refresh(sbom_task) + return sbom_task + async def generate_cyclonedx( self, project_key: safe.ProjectKey, diff --git a/atr/tasks/__init__.py b/atr/tasks/__init__.py index 7f3879b4..a2cc27a2 100644 --- a/atr/tasks/__init__.py +++ b/atr/tasks/__init__.py @@ -323,6 +323,8 @@ def resolve(task_type: sql.TaskType) -> Callable[..., Awaitable[results.Results return rat.check case sql.TaskType.SBOM_AUGMENT: return sbom.augment + case sql.TaskType.SBOM_CONVERT: + return sbom.convert_cyclonedx case sql.TaskType.SBOM_GENERATE_CYCLONEDX: return sbom.generate_cyclonedx case sql.TaskType.SBOM_OSV_SCAN: diff --git a/atr/tasks/sbom.py b/atr/tasks/sbom.py index c96e6a6e..33657e7e 100644 --- a/atr/tasks/sbom.py +++ b/atr/tasks/sbom.py @@ -40,6 +40,14 @@ import atr.util as util _CONFIG: Final = config.get() +class ConvertCycloneDX(schema.Strict): + """Arguments for the task to generate a CycloneDX SBOM.""" + + artifact_path: str = schema.description("Absolute path to the artifact") + output_path: str = schema.description("Absolute path where the generated SBOM JSON should be written") + revision: safe.RevisionNumber = schema.description("Revision number") + + class GenerateCycloneDX(schema.Strict): """Arguments for the task to generate a CycloneDX SBOM.""" @@ -47,6 +55,14 @@ class GenerateCycloneDX(schema.Strict): output_path: str = schema.description("Absolute path where the generated SBOM JSON should be written") +class SBOMConversionError(Exception): + """Custom exception for SBOM conversion failures.""" + + def __init__(self, message: str, details: dict[str, Any] | None = None) -> None: + super().__init__(message) + self.details = details or {} + + class SBOMGenerationError(Exception): """Custom exception for SBOM generation failures.""" @@ -83,12 +99,10 @@ class ScoreArgs(FileArgs): @checks.with_model(FileArgs) async def augment(args: FileArgs) -> results.Results | None: - project_str = str(args.project_key) - version_str = str(args.version_key) revision_str = str(args.revision_number) path_str = str(args.file_path) - base_dir = paths.get_unfinished_dir() / project_str / version_str / revision_str + base_dir = paths.get_unfinished_dir_for(args.project_key, args.version_key, args.revision_number) if not await aiofiles.os.path.isdir(base_dir): raise SBOMScoringError("Revision directory does not exist", {"base_dir": str(base_dir)}) full_path = base_dir / path_str @@ -97,6 +111,8 @@ async def augment(args: FileArgs) -> results.Results | None: raise SBOMScoringError("SBOM file does not exist", {"file_path": path_str}) # Read from the old revision bundle = sbom.utilities.path_to_bundle(full_path) + if not bundle: + raise SBOMScoringError("Could not load bundle") patch_ops = await sbom.utilities.bundle_to_ntia_patch(bundle) new_full_path: pathlib.Path | None = None new_full_path_str: str | None = None @@ -128,6 +144,21 @@ async def augment(args: FileArgs) -> results.Results | None: ) [email protected]_model(ConvertCycloneDX) +async def convert_cyclonedx(args: ConvertCycloneDX) -> results.Results | None: + """Generate a JSON CycloneDX SBOM from a given XML SBOM.""" + try: + result_data = await _convert_cyclonedx_core(args.artifact_path, args.output_path, str(args.revision)) + log.info(f"Successfully converted CycloneDX SBOM for {args.artifact_path}") + msg = result_data["message"] + if not isinstance(msg, str): + raise SBOMConversionError(f"Invalid message type: {type(msg)}") + return results.SBOMConvert(kind="sbom_convert", path=args.output_path, bom_version=result_data.get("version")) + except (archives.ExtractionError, SBOMGenerationError) as e: + log.error(f"SBOM conversion failed for {args.artifact_path}: {e}") + raise + + @checks.with_model(GenerateCycloneDX) async def generate_cyclonedx(args: GenerateCycloneDX) -> results.Results | None: """Generate a CycloneDX SBOM for the given artifact and write it to the output path.""" @@ -159,6 +190,8 @@ async def osv_scan(args: FileArgs) -> results.Results | None: if not (full_path_str.endswith(".cdx.json") and await aiofiles.os.path.isfile(full_path)): raise SBOMScanningError("SBOM file does not exist", {"file_path": path_str}) bundle = sbom.utilities.path_to_bundle(full_path) + if not bundle: + raise SBOMScanningError("Could not load bundle") vulnerabilities, ignored = await sbom.osv.scan_bundle(bundle) patch_ops = await sbom.utilities.bundle_to_vuln_patch(bundle, vulnerabilities) components = [] @@ -259,8 +292,10 @@ async def score_tool(args: ScoreArgs) -> results.Results | None: if not (full_path_str.endswith(".cdx.json") and await aiofiles.os.path.isfile(full_path)): raise SBOMScoringError("SBOM file does not exist", {"file_path": path_str}) bundle = sbom.utilities.path_to_bundle(full_path) + if not bundle: + raise SBOMScoringError("Could not load bundle") version, properties = sbom.utilities.get_props_from_bundle(bundle) - warnings, errors = sbom.conformance.ntia_2021_issues(bundle.bom) + warnings, errors = sbom.conformance.ntia_2021_issues(bundle) # TODO: Could update the ATR version with a constant showing last change to the augment/scan # tools so we know if it's outdated outdated = sbom.tool.plugin_outdated_version(bundle.bom) @@ -302,7 +337,7 @@ async def score_tool(args: ScoreArgs) -> results.Results | None: vulnerabilities=[v.model_dump_json() for v in vulnerabilities], prev_licenses=[w.model_dump_json() for w in prev_licenses] if prev_licenses else None, prev_vulnerabilities=[v.model_dump_json() for v in prev_vulnerabilities] if prev_vulnerabilities else None, - atr_props=properties, + atr_props=[{p.name: p.value or ""} for p in properties], cli_errors=cli_errors, ) @@ -325,6 +360,34 @@ def _extracted_dir(temp_dir: str) -> str | None: return extract_dir +async def _convert_cyclonedx_core(artifact_path: str, output_path: str, revision_str: str) -> dict[str, Any]: + """Core logic to convert XML CycloneDX SBOM to JSON.""" + log.info(f"Generating CycloneDX JSON SBOM for {artifact_path} -> {output_path}") + + # TODO: Should create a new revision here rather than in the caller + bundle = sbom.utilities.path_to_bundle(pathlib.Path(artifact_path)) + if not bundle: + raise SBOMConversionError("Could not load bundle") + sbom.utilities.apply_patch("conversion to JSON", revision_str, bundle, []) + outputter = sbom.utilities.bundle_outputter(bundle) + text = outputter.output_as_string(indent=2) + + try: + async with aiofiles.open(output_path, "w", encoding="utf-8") as f: + await f.write(text) + log.info(f"Successfully wrote JSON SBOM to {output_path}") + except Exception as write_err: + log.exception(f"Failed to write SBOM JSON to {output_path}: {write_err}") + raise SBOMConversionError(f"Failed to write SBOM to {output_path}: {write_err}") from write_err + + return { + "message": "Successfully generated and saved CycloneDX SBOM", + "sbom": text, + "format": "CycloneDX", + "version": str(bundle.bom.version), + } + + async def _generate_cyclonedx_core(artifact_path: str, output_path: str) -> dict[str, Any]: """Core logic to generate CycloneDX SBOM on failure.""" log.info(f"Generating CycloneDX SBOM for {artifact_path} -> {output_path}") diff --git a/atr/templates/draft-tools.html b/atr/templates/draft-tools.html index 38851178..0dc4ef47 100644 --- a/atr/templates/draft-tools.html +++ b/atr/templates/draft-tools.html @@ -36,4 +36,12 @@ <p>Generate a CycloneDX Software Bill of Materials (SBOM) file for this artifact.</p> {{ sbom_form|safe }} {% endif %} + {% if file_path.endswith(".cdx.xml") and is_viewing_as_admin_fn(current_user.uid) %} + <h3>Convert SBOM</h3> + <div class="alert-info"> + <p>NOTE: This functionality is in early release.</p> + </div> + <p>Convert this XML CycloneDX Software Bill of Materials (SBOM) into a JSON equivalent.</p> + {{ sbom_convert_form|safe }} + {% endif %} {% endblock content %} diff --git a/pip-audit.requirements b/pip-audit.requirements index c70194ab..0d628f41 100644 --- a/pip-audit.requirements +++ b/pip-audit.requirements @@ -5,7 +5,7 @@ aiofiles==25.1.0 # tooling-trusted-releases aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.3 +aiohttp==3.13.4 # via # asfpy # asfquart @@ -30,7 +30,7 @@ arrow==1.4.0 # via isoduration asfpy==0.58 # via asfquart -asfquart @ git+https://github.com/apache/infrastructure-asfquart.git@788bbbcca5552644ed669cd0bbd4bee295da8f9a +asfquart @ git+https://github.com/apache/infrastructure-asfquart.git@fab20db357dd689ae5863c86d9eb1b07ae690b83 # via tooling-trusted-releases asyncssh==2.22.0 # via tooling-trusted-releases @@ -72,7 +72,7 @@ colorama==0.4.6 # click # djlint # tqdm -cryptography==46.0.5 +cryptography==46.0.6 # via # asfpy # asyncssh @@ -108,7 +108,7 @@ editorconfig==0.17.1 # jsbeautifier email-validator==2.3.0 # via tooling-trusted-releases -exarch==0.2.8 +exarch==0.2.9 # via tooling-trusted-releases ezt==1.1 # via @@ -175,7 +175,7 @@ jsbeautifier==1.15.4 # via # cssbeautifier # djlint -json5==0.13.0 +json5==0.14.0 # via djlint jsonpointer==3.1.1 # via jsonschema @@ -258,7 +258,7 @@ pydantic-core==2.41.5 # pydantic-xml pydantic-xml==2.19.0 # via tooling-trusted-releases -pygments==2.19.2 +pygments==2.20.0 # via rich pyhumps==3.8.0 # via quart-schema @@ -271,7 +271,7 @@ python-dateutil==2.9.0.post0 # strictyaml python-decouple==3.8 # via tooling-trusted-releases -python-discovery==1.2.0 +python-discovery==1.2.1 # via virtualenv python-gnupg==0.5.6 # via tooling-trusted-releases @@ -301,7 +301,7 @@ referencing==0.37.0 # cyclonedx-python-lib # jsonschema # jsonschema-specifications -regex==2026.2.28 +regex==2026.3.32 # via djlint requests==2.33.0 # via asfpy @@ -317,7 +317,7 @@ rpds-py==0.30.0 # via # jsonschema # referencing -ruff==0.15.7 +ruff==0.15.8 semver==3.0.4 # via tooling-trusted-releases six==1.17.0 diff --git a/typestubs/cyclonedx/model/bom.pyi b/typestubs/cyclonedx/model/bom.pyi new file mode 100644 index 00000000..d543f8c0 --- /dev/null +++ b/typestubs/cyclonedx/model/bom.pyi @@ -0,0 +1,44 @@ +from collections.abc import Sequence +from datetime import datetime +from typing import Any, Optional, Type, Dict + +from cyclonedx.model.component import Component +from cyclonedx.model.contact import OrganizationalEntity +from py_serializable import _JsonSerializable, _XmlSerializable, _T + + +class BomMetaData: + component: Optional[Component] + supplier: Optional[OrganizationalEntity] + authors: Any + timestamp: datetime + tools: Any + + +class Bom(_JsonSerializable, _XmlSerializable): + def __init__(self, **kwargs: Any) -> None: ... + + @property + def version(self) -> int: ... + @version.setter + def version(self, version: int) -> None: ... + + @property + def metadata(self) -> BomMetaData: ... + @metadata.setter + def metadata(self, metadata: BomMetaData) -> None: ... + + @property + def components(self) -> Sequence[Component]: ... + @components.setter + def components(self, components: Any) -> None: ... + + @property + def properties(self) -> Any: ... + @properties.setter + def properties(self, properties: Any) -> None: ... + + @property + def dependencies(self) -> Any: ... + @dependencies.setter + def dependencies(self, dependencies: Any) -> None: ... diff --git a/typestubs/cyclonedx/model/component.pyi b/typestubs/cyclonedx/model/component.pyi new file mode 100644 index 00000000..6ff78ec9 --- /dev/null +++ b/typestubs/cyclonedx/model/component.pyi @@ -0,0 +1,48 @@ +from enum import Enum +from typing import Any, Optional + +from cyclonedx.model.bom_ref import BomRef +from cyclonedx.model.contact import OrganizationalEntity +from cyclonedx.model.license import LicenseRepository +from packageurl import PackageURL +from py_serializable import _JsonSerializable, _XmlSerializable + + +class ComponentScope(str, Enum): + REQUIRED = "required" + OPTIONAL = "optional" + EXCLUDED = "excluded" + + +class ComponentType(str, Enum): + APPLICATION = "application" + CONTAINER = "container" + CRYPTOGRAPHIC_ASSET = "cryptographic-asset" + DATA = "data" + DEVICE = "device" + DEVICE_DRIVER = "device-driver" + FILE = "file" + FIRMWARE = "firmware" + FRAMEWORK = "framework" + LIBRARY = "library" + MACHINE_LEARNING_MODEL = "machine-learning-model" + OPERATING_SYSTEM = "operating-system" + PLATFORM = "platform" + + +class Swid: ... + + +class Component(_JsonSerializable, _XmlSerializable): + name: str + version: Optional[str] + type: ComponentType + supplier: Optional[OrganizationalEntity] + cpe: Optional[str] + purl: Optional[PackageURL] + swid: Optional[Swid] + scope: Optional[ComponentScope] + licenses: LicenseRepository + bom_ref: BomRef + + def __init__(self, *, component_type: ComponentType, name: str, **kwargs: Any) -> None: ... diff --git a/typestubs/cyclonedx/output/__init__.pyi b/typestubs/cyclonedx/output/__init__.pyi new file mode 100644 index 00000000..49c0631a --- /dev/null +++ b/typestubs/cyclonedx/output/__init__.pyi @@ -0,0 +1,22 @@ +from abc import abstractmethod +from typing import Any, Optional, Union + +from cyclonedx.model.bom import Bom +from cyclonedx.schema import OutputFormat, SchemaVersion + + +class BaseOutput: + @abstractmethod + def output_as_string( + self, + *, + indent: Optional[Union[int, str]] = ..., + **kwargs: Any, + ) -> str: ... + + +def make_outputter( + bom: Bom, + output_format: OutputFormat, + schema_version: SchemaVersion, +) -> BaseOutput: ... diff --git a/typestubs/py_serializable/__init__.pyi b/typestubs/py_serializable/__init__.pyi new file mode 100644 index 00000000..da458fc6 --- /dev/null +++ b/typestubs/py_serializable/__init__.pyi @@ -0,0 +1,94 @@ +from collections.abc import Callable +from enum import Enum +from io import TextIOBase +from typing import Any, Iterable, Self, TypeVar, overload +from xml.etree.ElementTree import Element + +_T = TypeVar("_T") +_E = TypeVar("_E") +_F = TypeVar("_F", bound=Callable[..., Any]) + + +class ViewType: ... + + +class SerializationType(str, Enum): + JSON = "JSON" + XML = "XML" + + +class XmlArraySerializationType(Enum): ... + + +class XmlStringSerializationType(Enum): ... + + +class _JsonSerializable: + @classmethod + def from_json(cls, data: dict[str, Any]) -> Self | None: ... + def as_json(self, view_: type[ViewType] | None = ...) -> str: ... + + +class _XmlSerializable: + @classmethod + def from_xml( + cls, + data: TextIOBase | Element, + default_namespace: str | None = ..., + ) -> Self | None: ... + def as_xml( + self, + view_: type[ViewType] | None = ..., + as_string: bool = ..., + element_name: str | None = ..., + xmlns: str | None = ..., + ) -> Element | str: ... + + +class ObjectMetadataLibrary: ... + + +# Typed as a no-op to avoid the Union[Type[_T], Type[_JsonSerializable], Type[_XmlSerializable]] +# that py_serializable produces using Union as a stand-in for the unsupported Intersection type. +# The serialization methods are added to individual classes via their own stubs. +@overload +def serializable_class( + cls: None = ..., + *, + name: str | None = ..., + serialization_types: Iterable[SerializationType] | None = ..., + ignore_during_deserialization: Iterable[str] | None = ..., + ignore_unknown_during_deserialization: bool = ..., +) -> Callable[[type[_T]], type[_T]]: ... +@overload +def serializable_class( + cls: type[_T], + *, + name: str | None = ..., + serialization_types: Iterable[SerializationType] | None = ..., + ignore_during_deserialization: Iterable[str] | None = ..., + ignore_unknown_during_deserialization: bool = ..., +) -> type[_T]: ... + + +@overload +def serializable_enum(cls: None = ...) -> Callable[[type[_E]], type[_E]]: ... +@overload +def serializable_enum(cls: type[_E]) -> type[_E]: ... + + +def type_mapping(type_: type) -> Callable[[_F], _F]: ... +def include_none( + view_: type[ViewType] | None = ..., none_value: Any | None = ... +) -> Callable[[_F], _F]: ... +def json_name(name: str) -> Callable[[_F], _F]: ... +def string_format(format_: str) -> Callable[[_F], _F]: ... +def view(view_: type[ViewType]) -> Callable[[_F], _F]: ... +def xml_attribute() -> Callable[[_F], _F]: ... +def xml_array( + array_type: XmlArraySerializationType, child_name: str +) -> Callable[[_F], _F]: ... +def xml_string(string_type: XmlStringSerializationType) -> Callable[[_F], _F]: ... +def xml_name(name: str) -> Callable[[_F], _F]: ... +def xml_sequence(sequence: int) -> Callable[[_F], _F]: ... +def allow_none_value_for_dict(key: str) -> Callable[[_F], _F]: ... diff --git a/uv.lock b/uv.lock index 7240d59c..5d005be2 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = "==3.13.*" [options] -exclude-newer = "2026-03-25T17:17:12Z" +exclude-newer = "2026-03-30T10:05:57Z" [[package]] name = "aiofiles" @@ -25,7 +25,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.3" +version = "3.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -36,25 +36,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38", size = 7859748, upload-time = "2026-03-28T17:19:40.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, - { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, - { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, - { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, - { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, - { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, - { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, - { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, - { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538", size = 745744, upload-time = "2026-03-28T17:16:44.67Z" }, + { url = "https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e", size = 498178, upload-time = "2026-03-28T17:16:46.766Z" }, + { url = "https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3", size = 498331, upload-time = "2026-03-28T17:16:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2", size = 1744414, upload-time = "2026-03-28T17:16:50.968Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21", size = 1719226, upload-time = "2026-03-28T17:16:53.173Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0", size = 1782110, upload-time = "2026-03-28T17:16:55.362Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e", size = 1884809, upload-time = "2026-03-28T17:16:57.734Z" }, + { url = "https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a", size = 1764938, upload-time = "2026-03-28T17:17:00.221Z" }, + { url = "https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f", size = 1570697, upload-time = "2026-03-28T17:17:02.985Z" }, + { url = "https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c", size = 1702258, upload-time = "2026-03-28T17:17:05.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500", size = 1740287, upload-time = "2026-03-28T17:17:07.712Z" }, + { url = "https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7", size = 1753011, upload-time = "2026-03-28T17:17:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073", size = 1566359, upload-time = "2026-03-28T17:17:12.433Z" }, + { url = "https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069", size = 1785537, upload-time = "2026-03-28T17:17:14.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5", size = 1740752, upload-time = "2026-03-28T17:17:17.387Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70", size = 433187, upload-time = "2026-03-28T17:17:19.523Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3", size = 459778, upload-time = "2026-03-28T17:17:21.964Z" }, ] [[package]] @@ -175,7 +175,7 @@ wheels = [ [[package]] name = "asfquart" version = "0.1.13" -source = { git = "https://github.com/apache/infrastructure-asfquart.git?rev=main#788bbbcca5552644ed669cd0bbd4bee295da8f9a" } +source = { git = "https://github.com/apache/infrastructure-asfquart.git?rev=main#fab20db357dd689ae5863c86d9eb1b07ae690b83" } dependencies = [ { name = "aiohttp" }, { name = "asfpy" }, @@ -377,41 +377,41 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.5" +version = "46.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, - { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, ] [[package]] @@ -582,16 +582,16 @@ wheels = [ [[package]] name = "exarch" -version = "0.2.8" +version = "0.2.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/7d/e1a9c9967aac5cfe52c29c664fe0cf2b077edafc1b644d7e3a41464611ee/exarch-0.2.8-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5db6ca025e5af3434253340f10c981dd3da3cce18dd762a7d48d3cb034feb8ad", size = 1236442, upload-time = "2026-03-15T02:48:20.278Z" }, - { url = "https://files.pythonhosted.org/packages/bd/76/a4d0cff31e57a7bbfd8ed67f80c64ffe8fd1f3a4323c7907568a8790a6bf/exarch-0.2.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:063a613713c74701be18604381ecf8e1f0266919c20b34fc05dae8ef5a405ca0", size = 1091798, upload-time = "2026-03-15T02:48:22.721Z" }, - { url = "https://files.pythonhosted.org/packages/3d/23/fac2da7f9876c249a895934a1522f963819172b046092733843a388dd2d0/exarch-0.2.8-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d9a123f08828aa08f504c06f22d2d2c7e40b456845029d80a18f847ace44aac0", size = 1300032, upload-time = "2026-03-15T02:48:24.675Z" }, - { url = "https://files.pythonhosted.org/packages/81/85/87b658a6f0ed7de1e7b369f8c565255cc1e6da2b86083c9238837ed72bda/exarch-0.2.8-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:397866e49e5113fc13583121df17604ee6eb4cb3dc5ba16b9bb6a54562d379f4", size = 1389152, upload-time = "2026-03-15T02:48:26.672Z" }, - { url = "https://files.pythonhosted.org/packages/49/8e/86e5f22f6432608353a64174df155a7c53f6e006401d7c5c49a88d6978a7/exarch-0.2.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0bcc86d4527b9b5f979ac50e8827cf1fd24376e7865877ddc3fea59bead3f56b", size = 1471165, upload-time = "2026-03-15T02:48:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/56/1e/a300b1655cd7f3bc942fa78f71248b80275f4e0b85f42cda07e475a8ff05/exarch-0.2.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:95ec3fecd5668eb075e9b06bf06c8d163fb1d4f53923cf5b9ad6dbe0891a5f13", size = 1595388, upload-time = "2026-03-15T02:48:30.252Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c8/9c95dc56017d0dbecea62758da185bcdbf1c458ea2c7f30bc8df6809f5ab/exarch-0.2.8-cp39-abi3-win_amd64.whl", hash = "sha256:ed7f96310997f7459f856708fc4bbaa6e55b4b50801097d2e78343b284762426", size = 1067310, upload-time = "2026-03-15T02:48:31.624Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/171a44903d8aa2332e3d6d55e6a7597ea4c5941ed8325f2a1449635ac31c/exarch-0.2.9-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:eac426747b38a8abe6a36a189bdcdd055950679f669fbf0ddf310c46eb3513db", size = 1246226, upload-time = "2026-03-25T21:58:10.42Z" }, + { url = "https://files.pythonhosted.org/packages/42/fc/2f809e3b373548ad298bbb7bd97c592d0a6dbae2abc6b4220a7993bdfede/exarch-0.2.9-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:506697e367450a5ffafb16b12d3c4da64671766eca687996d206e9d6e4b7f53a", size = 1099968, upload-time = "2026-03-25T21:58:12.294Z" }, + { url = "https://files.pythonhosted.org/packages/f2/54/a32835a445dcde56ffaf9826e8ad7738fa8ec48a87ab22ddd92561245cdb/exarch-0.2.9-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:94e1cdd48f7668f97380b8507df2f8e87b0c801ee573a7f976cdd93576be522a", size = 1307715, upload-time = "2026-03-25T21:58:13.716Z" }, + { url = "https://files.pythonhosted.org/packages/ba/37/b39ecd16eadb81cdd7266660a25409e751bb2779850b4f4e1c95cbcfd7cf/exarch-0.2.9-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:41cf45de8bdf102c7f70f98ab0a55ad657ca3f1df1a0bca937734edd2ac90fec", size = 1399745, upload-time = "2026-03-25T21:58:15.134Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a0/70711986b43590db3e344644d47605f57dab5cc91cf2c19948f4a909fd55/exarch-0.2.9-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2ee98071e4e51cbb5640b02a0286cca938ba272d6ef2c3db18393a71e7c35c7a", size = 1478421, upload-time = "2026-03-25T21:58:16.535Z" }, + { url = "https://files.pythonhosted.org/packages/ae/bc/12823a091551215f41cbdb713c52332a1ea418686280458349f66e87a61e/exarch-0.2.9-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ffd98180cf397338f752f281f6c85ca1989822809fbe87dd5aea73f6971e1f54", size = 1605251, upload-time = "2026-03-25T21:58:17.985Z" }, + { url = "https://files.pythonhosted.org/packages/2e/40/0eeab4d53394a3915888f78ad5cd66615e42b5dfaf67ab83594fe219ca46/exarch-0.2.9-cp39-abi3-win_amd64.whl", hash = "sha256:594889809eee16eeeb50752234eef50c74d760bf94a9631fa96a2229680f39a8", size = 1076429, upload-time = "2026-03-25T21:58:19.587Z" }, ] [[package]] @@ -867,11 +867,11 @@ wheels = [ [[package]] name = "json5" -version = "0.13.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/6f8906aaf67d501e259b0adab4d312945bb7211e8b8d4dcc77c92320edaa/json5-0.14.0.tar.gz", hash = "sha256:b3f492fad9f6cdbced8b7d40b28b9b1c9701c5f561bef0d33b81c2ff433fefcb", size = 52656, upload-time = "2026-03-27T22:50:48.108Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl", hash = "sha256:56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a", size = 36271, upload-time = "2026-03-27T22:50:47.073Z" }, ] [[package]] @@ -1376,11 +1376,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.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/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } 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/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -1493,15 +1493,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.0" +version = "1.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, ] [[package]] @@ -1636,42 +1636,42 @@ wheels = [ [[package]] name = "regex" -version = "2026.2.28" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311, upload-time = "2026-02-28T02:17:22.591Z" }, - { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285, upload-time = "2026-02-28T02:17:24.355Z" }, - { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051, upload-time = "2026-02-28T02:17:26.722Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842, upload-time = "2026-02-28T02:17:29.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083, upload-time = "2026-02-28T02:17:31.363Z" }, - { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412, upload-time = "2026-02-28T02:17:33.248Z" }, - { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101, upload-time = "2026-02-28T02:17:35.053Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260, upload-time = "2026-02-28T02:17:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311, upload-time = "2026-02-28T02:17:39.855Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876, upload-time = "2026-02-28T02:17:42.317Z" }, - { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632, upload-time = "2026-02-28T02:17:45.073Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320, upload-time = "2026-02-28T02:17:47.192Z" }, - { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152, upload-time = "2026-02-28T02:17:49.067Z" }, - { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398, upload-time = "2026-02-28T02:17:50.744Z" }, - { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282, upload-time = "2026-02-28T02:17:53.074Z" }, - { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382, upload-time = "2026-02-28T02:17:54.888Z" }, - { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541, upload-time = "2026-02-28T02:17:56.813Z" }, - { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984, upload-time = "2026-02-28T02:17:58.538Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509, upload-time = "2026-02-28T02:18:00.208Z" }, - { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429, upload-time = "2026-02-28T02:18:02.328Z" }, - { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422, upload-time = "2026-02-28T02:18:04.23Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175, upload-time = "2026-02-28T02:18:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044, upload-time = "2026-02-28T02:18:08.736Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056, upload-time = "2026-02-28T02:18:10.777Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743, upload-time = "2026-02-28T02:18:13.025Z" }, - { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633, upload-time = "2026-02-28T02:18:16.84Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862, upload-time = "2026-02-28T02:18:18.892Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788, upload-time = "2026-02-28T02:18:21.475Z" }, - { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184, upload-time = "2026-02-28T02:18:23.492Z" }, - { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137, upload-time = "2026-02-28T02:18:25.375Z" }, - { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682, upload-time = "2026-02-28T02:18:27.205Z" }, - { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735, upload-time = "2026-02-28T02:18:29.015Z" }, +version = "2026.3.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/93/5ab3e899c47fa7994e524447135a71cd121685a35c8fe35029005f8b236f/regex-2026.3.32.tar.gz", hash = "sha256:f1574566457161678297a116fa5d1556c5a4159d64c5ff7c760e7c564bf66f16", size = 415605, upload-time = "2026-03-28T21:49:22.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/ba/9c1819f302b42b5fbd4139ead6280e9ec37d19bbe33379df0039b2a57bb4/regex-2026.3.32-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c6d9c6e783b348f719b6118bb3f187b2e138e3112576c9679eb458cc8b2e164b", size = 490394, upload-time = "2026-03-28T21:46:58.112Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0b/f62b0ce79eb83ca82fffea1736289d29bc24400355968301406789bcebd2/regex-2026.3.32-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f21ae18dfd15752cdd98d03cbd7a3640be826bfd58482a93f730dbd24d7b9fb", size = 291993, upload-time = "2026-03-28T21:47:00.198Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d8/ba0f8f81f88cd20c0b27acc123561ac5495ea33f800f0b8ebed2038b23eb/regex-2026.3.32-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:844d88509c968dd44b30daeefac72b038b1bf31ac372d5106358ab01d393c48b", size = 289618, upload-time = "2026-03-28T21:47:02.269Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0d/b47a0e68bc511c195ff129c0311a4cd79b954b8676193a9d03a97c623a91/regex-2026.3.32-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fc918cd003ba0d066bf0003deb05a259baaaab4dc9bd4f1207bbbe64224857a", size = 796427, upload-time = "2026-03-28T21:47:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/32b05aa8fde7789ba316533c0f30e87b6b5d38d6d7f8765eadc5aab84671/regex-2026.3.32-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbc458a292aee57d572075f22c035fa32969cdb7987d454e3e34d45a40a0a8b4", size = 865850, upload-time = "2026-03-28T21:47:05.982Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/828d8095501f237b83f630d4069eea8c0e5cb6a204e859cf0b67c223ce12/regex-2026.3.32-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:987cdfcfb97a249abc3601ad53c7de5c370529f1981e4c8c46793e4a1e1bfe8e", size = 913578, upload-time = "2026-03-28T21:47:08.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f8/acf1eb80f58852e85bd39a6ddfa78ce2243ddc8de8da7582e6ba657da593/regex-2026.3.32-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5d88fa37ba5e8a80ca8d956b9ea03805cfa460223ac94b7d4854ee5e30f3173", size = 801536, upload-time = "2026-03-28T21:47:10.206Z" }, + { url = "https://files.pythonhosted.org/packages/9f/05/986cdf8d12693451f5889aaf4ea4f65b2c49b1152ae814fa1fb75439e40b/regex-2026.3.32-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d082be64e51671dd5ee1c208c92da2ddda0f2f20d8ef387e57634f7e97b6aae", size = 776226, upload-time = "2026-03-28T21:47:12.891Z" }, + { url = "https://files.pythonhosted.org/packages/32/02/945a6a2348ca1c6608cb1747275c8affd2ccd957d4885c25218a86377912/regex-2026.3.32-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1d7fa44aece1fa02b8927441614c96520253a5cad6a96994e3a81e060feed55", size = 785933, upload-time = "2026-03-28T21:47:14.795Z" }, + { url = "https://files.pythonhosted.org/packages/53/12/c5bab6cc679ad79a45427a98c4e70809586ac963c5ad54a9217533c4763e/regex-2026.3.32-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d478a2ca902b6ef28ffc9521e5f0f728d036abe35c0b250ee8ae78cfe7c5e44e", size = 860671, upload-time = "2026-03-28T21:47:16.985Z" }, + { url = "https://files.pythonhosted.org/packages/bf/68/8d85f98c2443469facabef62b82b851d369b13f92bec2ca7a3808deaa47b/regex-2026.3.32-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2820d2231885e97aff0fcf230a19ebd5d2b5b8a1ba338c20deb34f16db1c7897", size = 765335, upload-time = "2026-03-28T21:47:18.872Z" }, + { url = "https://files.pythonhosted.org/packages/89/a7/d8a9c270916107a501fca63b748547c6c77e570d19f16a29b557ce734f3d/regex-2026.3.32-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc8ced733d6cd9af5e412f256a32f7c61cd2d7371280a65c689939ac4572499f", size = 851913, upload-time = "2026-03-28T21:47:20.793Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8e/03d392b26679914ccf21f83d18ad4443232d2f8c3e2c30a962d4e3918d9c/regex-2026.3.32-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:847087abe98b3c1ebf1eb49d6ef320dbba75a83ee4f83c94704580f1df007dd4", size = 788447, upload-time = "2026-03-28T21:47:22.628Z" }, + { url = "https://files.pythonhosted.org/packages/cf/df/692227d23535a50604333068b39eb262626db780ab1e1b19d83fc66853aa/regex-2026.3.32-cp313-cp313-win32.whl", hash = "sha256:d21a07edddb3e0ca12a8b8712abc8452481c3d3db19ae87fc94e9842d005964b", size = 266834, upload-time = "2026-03-28T21:47:24.778Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/13e4e56adc16ba607cffa1fe880f233eb9ded8ab8a8580619683c9e4ce48/regex-2026.3.32-cp313-cp313-win_amd64.whl", hash = "sha256:3c054e39a9f85a3d76c62a1d50c626c5e9306964eaa675c53f61ff7ec1204bbb", size = 277972, upload-time = "2026-03-28T21:47:26.627Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1c/80a86dbb2b416fec003b1801462bdcebbf1d43202ed5acb176e99c1ba369/regex-2026.3.32-cp313-cp313-win_arm64.whl", hash = "sha256:b2e9c2ea2e93223579308263f359eab8837dc340530b860cb59b713651889f14", size = 270649, upload-time = "2026-03-28T21:47:28.551Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/e38372da599dc1c39c599907ec535016d110034bd3701ce36554f59767ef/regex-2026.3.32-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5d86e3fb08c94f084a625c8dc2132a79a3a111c8bf6e2bc59351fa61753c2f6e", size = 494495, upload-time = "2026-03-28T21:47:30.642Z" }, + { url = "https://files.pythonhosted.org/packages/5f/27/6e29ece8c9ce01001ece1137fa21c8707529c2305b22828f63623b0eb262/regex-2026.3.32-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b6f366a5ef66a2df4d9e68035cfe9f0eb8473cdfb922c37fac1d169b468607b0", size = 293988, upload-time = "2026-03-28T21:47:32.553Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/8752e18bb87a2fe728b73b0f83c082eb162a470766063f8028759fb26844/regex-2026.3.32-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b8fca73e16c49dd972ce3a88278dfa5b93bf91ddef332a46e9443abe21ca2f7c", size = 292634, upload-time = "2026-03-28T21:47:34.651Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7b/d7729fe294e23e9c7c3871cb69d49059fa7d65fd11e437a2cbea43f6615d/regex-2026.3.32-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b953d9d496d19786f4d46e6ba4b386c6e493e81e40f9c5392332458183b0599d", size = 810532, upload-time = "2026-03-28T21:47:36.839Z" }, + { url = "https://files.pythonhosted.org/packages/fd/49/4dae7b000659f611b17b9c1541fba800b0569e4060debc4635ef1b23982c/regex-2026.3.32-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b565f25171e04d4fad950d1fa837133e3af6ea6f509d96166eed745eb0cf63bc", size = 871919, upload-time = "2026-03-28T21:47:39.192Z" }, + { url = "https://files.pythonhosted.org/packages/83/85/aa8ad3977b9399861db3df62b33fe5fef6932ee23a1b9f4f357f58f2094b/regex-2026.3.32-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f28eac18a8733a124444643a66ac96fef2c0ad65f50034e0a043b90333dc677f", size = 916550, upload-time = "2026-03-28T21:47:41.618Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c0/6379d7f5b59ff0656ba49cf666d5013ecee55e83245275b310b0ffc79143/regex-2026.3.32-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cdd508664430dd51b8888deb6c5b416d8de046b2e11837254378d31febe4a98", size = 814988, upload-time = "2026-03-28T21:47:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/2c/af/2dfddc64074bd9b70e27e170ee9db900542e2870210b489ad4471416ba86/regex-2026.3.32-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5c35d097f509cf7e40d20d5bee548d35d6049b36eb9965e8d43e4659923405b9", size = 786337, upload-time = "2026-03-28T21:47:46.076Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2f/4eb8abd705236402b4fe0e130971634deffb1855e2028bf02a2b7c0e841c/regex-2026.3.32-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:85c9b0c131427470a6423baa0a9330be6fd8c3630cc3ee6fdee03360724cbec5", size = 800029, upload-time = "2026-03-28T21:47:48.356Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2c/77d9ca2c9df483b51b4b1291c96d79c9ae301077841c4db39bc822f6b4c6/regex-2026.3.32-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e50af656c15e2723eeb7279c0837e07accc594b95ec18b86821a4d44b51b24bf", size = 865843, upload-time = "2026-03-28T21:47:50.762Z" }, + { url = "https://files.pythonhosted.org/packages/48/10/306f477a509f4eed699071b1f031d89edd5a2b5fa28c8ede5b2638eaba82/regex-2026.3.32-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4bc32b4dbdb4f9f300cf9f38f8ea2ce9511a068ffaa45ac1373ee7a943f1d810", size = 772473, upload-time = "2026-03-28T21:47:52.771Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f6/54bd83ec46ac037de2beb049afc9dd5d2769c6ecaadf7856254ce610e62a/regex-2026.3.32-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3e5d1802cba785210a4a800e63fcee7a228649a880f3bf7f2aadccb151a834b", size = 856805, upload-time = "2026-03-28T21:47:55.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/ee0e7d14de1fc6582d5782f072db6c61465a38a4142f88e175dda494b536/regex-2026.3.32-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ef250a3f5e93182193f5c927c5e9575b2cb14b80d03e258bc0b89cc5de076b60", size = 801875, upload-time = "2026-03-28T21:47:57.434Z" }, + { url = "https://files.pythonhosted.org/packages/8a/06/0fa9daca59d07b6aabd8e0468d3b86fd578576a157206fbcddbfc2298f7d/regex-2026.3.32-cp313-cp313t-win32.whl", hash = "sha256:9cf7036dfa2370ccc8651521fcbb40391974841119e9982fa312b552929e6c85", size = 269892, upload-time = "2026-03-28T21:47:59.674Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/77f16b5ad9f10ca574f03d84a354b359b0ac33f85054f2f2daafc9f7b807/regex-2026.3.32-cp313-cp313t-win_amd64.whl", hash = "sha256:c940e00e8d3d10932c929d4b8657c2ea47d2560f31874c3e174c0d3488e8b865", size = 281318, upload-time = "2026-03-28T21:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/c6/47/db4446faaea8d01c8315c9c89c7dc6abbb3305e8e712e9b23936095c4d58/regex-2026.3.32-cp313-cp313t-win_arm64.whl", hash = "sha256:ace48c5e157c1e58b7de633c5e257285ce85e567ac500c833349c363b3df69d4", size = 272366, upload-time = "2026-03-28T21:48:03.748Z" }, ] [[package]] @@ -1774,27 +1774,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, - { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, - { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, ] [[package]] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
