From 7dcbf4248d0c354210f6714e69698f5e57d520bd Mon Sep 17 00:00:00 2001 From: Mark Kurtz Date: Mon, 25 May 2026 13:47:47 -0400 Subject: [PATCH 1/2] Implement standardized CI/CD setup for Python with Rust setups --- .detect-secrets.scan.json | 131 + .devcontainer/Dockerfile | 45 + .devcontainer/devcontainer.json | 51 + .editorconfig | 75 - .env.example | 154 - .github/CODEOWNERS | 3 +- .github/actions/oci/build/action.yml | 55 + .github/actions/oci/publish/action.yml | 57 + .github/actions/oci/quality/action.yml | 52 + .github/actions/oci/security/action.yml | 48 + .github/actions/oci/tests/action.yml | 78 + .github/actions/project/docs/action.yml | 153 + .github/actions/project/publish/action.yml | 23 + .github/actions/project/quality/action.yml | 43 + .github/actions/project/security/action.yml | 50 + .github/actions/project/tests/action.yml | 94 + .github/actions/python/build/action.yml | 47 + .github/actions/python/publish/action.yml | 61 + .github/actions/python/quality/action.yml | 44 + .github/actions/python/security/action.yml | 43 + .github/actions/python/tests/action.yml | 128 + .github/actions/rust/build/action.yml | 43 + .github/actions/rust/publish/action.yml | 24 + .github/actions/rust/quality/action.yml | 45 + .github/actions/rust/security/action.yml | 48 + .github/actions/rust/tests/action.yml | 121 + .../actions/utility/setup-python/action.yml | 32 + .github/actions/utility/setup-rust/action.yml | 37 + .github/paths-filter.yml | 170 + .github/workflows/_build_container.yml | 75 - .github/workflows/_build_package.yml | 66 - .github/workflows/_docs.yml | 112 - .github/workflows/_link-check.yml | 61 - .github/workflows/_pr_comment.yml | 59 - .github/workflows/_quality.yml | 63 - .github/workflows/_security.yml | 48 - .github/workflows/_tests.yml | 123 - .github/workflows/development.yml | 96 - .github/workflows/main.yml | 62 - .github/workflows/nightly.yml | 129 - .github/workflows/pipeline-development.yml | 257 + .github/workflows/pipeline-main.yml | 223 + .github/workflows/pipeline-nightly.yml | 336 ++ .github/workflows/pipeline-release.yml | 294 ++ .github/workflows/pipeline-weekly.yml | 217 + .github/workflows/release.yml | 96 - ...eanup.yml => util-development-cleanup.yml} | 19 +- .github/workflows/util-pr-comment.yml | 162 + .github/workflows/weekly.yml | 37 - .gitignore | 20 +- .pre-commit-config.yaml | 37 - .yamllint | 29 + AGENTS.md | 20 +- Cargo.lock | 415 ++ Cargo.toml | 46 + DEVELOPING.md | 568 ++- Dockerfile | 128 +- README.md | 9 +- covgate.toml | 3 + crates/rustarium-core/Cargo.toml | 38 + crates/rustarium-core/src/lib.rs | 33 + crates/rustarium-core/src/sum.rs | 33 + .../rustarium-core/tests/test_integration.rs | 21 + cst.yaml | 44 + deny.toml | 242 + docker-compose.yml | 26 +- docs/getting-started/quickstart.md | 9 +- docs/guides/github-workflows.md | 5 +- docs/guides/index.md | 12 - docs/guides/repository-setup.md | 94 - docs/reference/cli.md | 5 + docs/reference/index.md | 38 +- docs/reference/python_api.md | 5 + docs/reference/python_coverage.md | 15 + docs/reference/rust_api.md | 3 + docs/reference/rust_coverage.md | 15 + docs/scripts/extra.js | 1 - docs/scripts/gen_ref_pages.py | 76 - docs/scripts/macros.py | 79 + docs/stylesheets/extra.css | 1 - llms-full.txt | 496 +- mkdocs.yml | 141 - pyproject.toml | 683 ++- scripts/check_links.py | 160 + scripts/generate_doc_tests.py | 170 + scripts/run_oci.py | 696 +++ src/rustarium/__init__.py | 59 +- src/rustarium/__main__.py | 95 +- src/rustarium/_rust.pyi | 20 + src/rustarium/client.py | 94 + src/rustarium/compat.py | 9 +- src/rustarium/exceptions.py | 47 + src/rustarium/logging.py | 2 +- src/rustarium/schemas/__init__.py | 37 + src/rustarium/schemas/config.py | 166 + src/rustarium/schemas/updates.py | 106 + taplo.toml | 25 + tests/README.md | 53 +- tests/{ => python}/integration/__init__.py | 0 .../integration/test_integration.py | 39 +- tests/{ => python}/unit/__init__.py | 0 tests/{ => python}/unit/test_compat.py | 0 tests/{ => python}/unit/test_init.py | 20 + tests/{ => python}/unit/test_logging.py | 0 tests/{ => python}/unit/test_settings.py | 2 +- tests/{ => python}/unit/test_version.py | 0 uv.lock | 4122 ++++++++++++++--- zensical.toml | 39 + 108 files changed, 10917 insertions(+), 2924 deletions(-) create mode 100644 .detect-secrets.scan.json create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json delete mode 100644 .editorconfig delete mode 100644 .env.example create mode 100644 .github/actions/oci/build/action.yml create mode 100644 .github/actions/oci/publish/action.yml create mode 100644 .github/actions/oci/quality/action.yml create mode 100644 .github/actions/oci/security/action.yml create mode 100644 .github/actions/oci/tests/action.yml create mode 100644 .github/actions/project/docs/action.yml create mode 100644 .github/actions/project/publish/action.yml create mode 100644 .github/actions/project/quality/action.yml create mode 100644 .github/actions/project/security/action.yml create mode 100644 .github/actions/project/tests/action.yml create mode 100644 .github/actions/python/build/action.yml create mode 100644 .github/actions/python/publish/action.yml create mode 100644 .github/actions/python/quality/action.yml create mode 100644 .github/actions/python/security/action.yml create mode 100644 .github/actions/python/tests/action.yml create mode 100644 .github/actions/rust/build/action.yml create mode 100644 .github/actions/rust/publish/action.yml create mode 100644 .github/actions/rust/quality/action.yml create mode 100644 .github/actions/rust/security/action.yml create mode 100644 .github/actions/rust/tests/action.yml create mode 100644 .github/actions/utility/setup-python/action.yml create mode 100644 .github/actions/utility/setup-rust/action.yml create mode 100644 .github/paths-filter.yml delete mode 100644 .github/workflows/_build_container.yml delete mode 100644 .github/workflows/_build_package.yml delete mode 100644 .github/workflows/_docs.yml delete mode 100644 .github/workflows/_link-check.yml delete mode 100644 .github/workflows/_pr_comment.yml delete mode 100644 .github/workflows/_quality.yml delete mode 100644 .github/workflows/_security.yml delete mode 100644 .github/workflows/_tests.yml delete mode 100644 .github/workflows/development.yml delete mode 100644 .github/workflows/main.yml delete mode 100644 .github/workflows/nightly.yml create mode 100644 .github/workflows/pipeline-development.yml create mode 100644 .github/workflows/pipeline-main.yml create mode 100644 .github/workflows/pipeline-nightly.yml create mode 100644 .github/workflows/pipeline-release.yml create mode 100644 .github/workflows/pipeline-weekly.yml delete mode 100644 .github/workflows/release.yml rename .github/workflows/{development_cleanup.yml => util-development-cleanup.yml} (68%) create mode 100644 .github/workflows/util-pr-comment.yml delete mode 100644 .github/workflows/weekly.yml delete mode 100644 .pre-commit-config.yaml create mode 100644 .yamllint create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 covgate.toml create mode 100644 crates/rustarium-core/Cargo.toml create mode 100644 crates/rustarium-core/src/lib.rs create mode 100644 crates/rustarium-core/src/sum.rs create mode 100644 crates/rustarium-core/tests/test_integration.rs create mode 100644 cst.yaml create mode 100644 deny.toml delete mode 100644 docs/guides/repository-setup.md create mode 100644 docs/reference/cli.md create mode 100644 docs/reference/python_api.md create mode 100644 docs/reference/python_coverage.md create mode 100644 docs/reference/rust_api.md create mode 100644 docs/reference/rust_coverage.md delete mode 100644 docs/scripts/extra.js delete mode 100644 docs/scripts/gen_ref_pages.py create mode 100644 docs/scripts/macros.py delete mode 100644 docs/stylesheets/extra.css delete mode 100644 mkdocs.yml create mode 100644 scripts/check_links.py create mode 100644 scripts/generate_doc_tests.py create mode 100644 scripts/run_oci.py create mode 100644 src/rustarium/_rust.pyi create mode 100644 src/rustarium/client.py create mode 100644 src/rustarium/exceptions.py create mode 100644 src/rustarium/schemas/__init__.py create mode 100644 src/rustarium/schemas/config.py create mode 100644 src/rustarium/schemas/updates.py create mode 100644 taplo.toml rename tests/{ => python}/integration/__init__.py (100%) rename tests/{ => python}/integration/test_integration.py (70%) rename tests/{ => python}/unit/__init__.py (100%) rename tests/{ => python}/unit/test_compat.py (100%) rename tests/{ => python}/unit/test_init.py (50%) rename tests/{ => python}/unit/test_logging.py (100%) rename tests/{ => python}/unit/test_settings.py (98%) rename tests/{ => python}/unit/test_version.py (100%) create mode 100644 zensical.toml diff --git a/.detect-secrets.scan.json b/.detect-secrets.scan.json new file mode 100644 index 0000000..f3a855b --- /dev/null +++ b/.detect-secrets.scan.json @@ -0,0 +1,131 @@ +{ + "version": "1.5.47", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "GitLabTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "IPPublicDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "OpenAIDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "PypiTokenDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TelegramBotTokenDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".detect-secrets.scan.json" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], + "results": {}, + "generated_at": "2026-05-25T17:09:18Z" +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..9d31a21 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,45 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM mcr.microsoft.com/devcontainers/python:3.10 + +# Install build dependencies and utilities +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + curl \ + git \ + libssl-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +# Install uv globally +RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ + mv /root/.local/bin/uv /usr/local/bin/uv && \ + mv /root/.local/bin/uvx /usr/local/bin/uvx + +# Install hatch globally via uv +RUN uv tool install hatch --to /usr/local/bin + +# Switch to vscode user to install Rust toolchain +USER vscode +ENV HOME=/home/vscode +ENV PATH=$HOME/.cargo/bin:$PATH + +# Install Rust stable and cargo utilities +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + +# Ensure cargo-nextest, maturin, and other essentials can be installed simply +RUN cargo install maturin cargo-nextest + +ENV SHELL=/bin/bash diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e0cecc4 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +// Copyright 2026 markurtz +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +{ + "name": "rustarium-dev-environment", + "build": { + "dockerfile": "Dockerfile", + "context": "." + }, + "customizations": { + "vscode": { + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.ruffEnabled": true, + "python.formatting.provider": "none", + "editor.formatOnSave": true, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.organizeImports": "always" + } + }, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + } + }, + "extensions": [ + "charliermarsh.ruff", + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "ms-python.python", + "ms-python.vscode-pylance", + "yzhang.markdown-all-in-one" + ] + } + }, + "remoteUser": "vscode", + "postCreateCommand": "uv sync --all-groups --all-extras" +} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 6b4eeb7..0000000 --- a/.editorconfig +++ /dev/null @@ -1,75 +0,0 @@ -# ============================================================================== -# EditorConfig for rustarium -# https://editorconfig.org -# -# This file enforces consistent coding styles across multiple editors and IDEs. -# It is designed to work out-of-the-box for 95% of standard programming -# environments and aligns with high-quality open-source standards. -# ============================================================================== - -# Top-most EditorConfig file for the project -root = true - -# ------------------------------------------------------------------------------ -# Default Settings (Applies to all files) -# ------------------------------------------------------------------------------ -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space -indent_size = 4 - -# ------------------------------------------------------------------------------ -# Web, Data, and Frontend (2 spaces) -# ------------------------------------------------------------------------------ -[*.{js,jsx,ts,tsx,vue,json,yml,yaml,toml,html,css,scss,xml}] -indent_style = space -indent_size = 2 - -# ------------------------------------------------------------------------------ -# Python (4 spaces, standard PEP-8) -# ------------------------------------------------------------------------------ -[*.py] -indent_style = space -indent_size = 4 - -# ------------------------------------------------------------------------------ -# Go (Tabs) -# ------------------------------------------------------------------------------ -[*.go] -indent_style = tab - -# ------------------------------------------------------------------------------ -# Rust, C, C++, Java, C# (4 spaces) -# ------------------------------------------------------------------------------ -[*.{rs,c,h,cpp,hpp,java,cs}] -indent_style = space -indent_size = 4 - -# ------------------------------------------------------------------------------ -# Shell Scripts and Docker (2 spaces) -# ------------------------------------------------------------------------------ -[*.{sh,bash}] -indent_style = space -indent_size = 2 - -[{Dockerfile,*.dockerfile}] -indent_style = space -indent_size = 2 - -# ------------------------------------------------------------------------------ -# Makefiles (Must use tabs for indentation) -# ------------------------------------------------------------------------------ -[{Makefile,*.mk}] -indent_style = tab - -# ------------------------------------------------------------------------------ -# Markdown Documents -# ------------------------------------------------------------------------------ -[*.md] -# Markdown allows double spaces at the end of a line for hard line breaks. -trim_trailing_whitespace = false -indent_style = space -indent_size = 2 diff --git a/.env.example b/.env.example deleted file mode 100644 index 2fef96d..0000000 --- a/.env.example +++ /dev/null @@ -1,154 +0,0 @@ -# ============================================================================== -# Environment Configuration Template -# ============================================================================== -# INSTRUCTIONS: -# 1. Copy this file and rename it to `.env` (this file is ignored by git). -# 2. Replace the placeholder values with your actual local configuration. -# 3. DO NOT commit actual secrets (passwords, API keys) to version control. -# 4. Use meaningful defaults for development where safe. -# ============================================================================== - -# ------------------------------------------------------------------------------ -# Core Application Settings -# ------------------------------------------------------------------------------ -# Application Environment (e.g., development, staging, production, testing) -APP_ENV=development -# Enable/disable debug mode (verbose logging, stack traces) -APP_DEBUG=true -APP_NAME="rustarium" -APP_VERSION="1.0.0" -# Base URL for the application (useful for generating absolute links, emails) -APP_URL=http://localhost:8080 -# Timezone context for the application -APP_TIMEZONE=UTC -# Default locale/language -APP_LOCALE=en - -# ------------------------------------------------------------------------------ -# Server / Network Configuration -# ------------------------------------------------------------------------------ -# Port the application server listens on -PORT=8080 -APP_PORT=8080 -# Host to bind to (0.0.0.0 for all interfaces, useful in Docker environments) -HOST=0.0.0.0 -# Allowed hosts/CORS origins (comma separated) -CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080 - -# ------------------------------------------------------------------------------ -# Database Configuration (Relational / SQL) -# ------------------------------------------------------------------------------ -# Connection type (e.g., postgres, mysql, sqlite, mssql) -DB_CONNECTION=postgres -DB_HOST=localhost -DB_PORT=5432 -DB_DATABASE=rustarium_dev -DB_USERNAME=admin -DB_PASSWORD=secret -# Database schema (if applicable, e.g., public for postgres) -DB_SCHEMA=public -# Full connection string alternative (often used by ORMs like Prisma or SQLAlchemy) -# DATABASE_URL=postgres://admin:secret@localhost:5432/rustarium_dev - -# ------------------------------------------------------------------------------ -# Cache / Key-Value Store / Message Brokers -# ------------------------------------------------------------------------------ -# Redis configuration -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD=null -REDIS_DB=0 -# REDIS_URL=redis://localhost:6379/0 - -# Memcached configuration -MEMCACHED_HOST=localhost -MEMCACHED_PORT=11211 - -# RabbitMQ / Celery / Message Queue -# RABBITMQ_URL=amqp://guest:guest@localhost:5672/ - -# ------------------------------------------------------------------------------ -# Security & Authentication -# ------------------------------------------------------------------------------ -# Application secret key (used for sessions, cookie signing, CSRF tokens) -APP_SECRET_KEY=your_super_secret_key_change_in_production -# JWT (JSON Web Token) configuration -JWT_SECRET=your_jwt_secret_key -JWT_EXPIRES_IN=86400 # e.g., in seconds (1 day) -# OAuth / Single Sign-On (SSO) generic providers -OAUTH_CLIENT_ID=your_oauth_client_id -OAUTH_CLIENT_SECRET=your_oauth_client_secret - -# ------------------------------------------------------------------------------ -# Email / SMTP Configuration -# ------------------------------------------------------------------------------ -MAIL_MAILER=smtp -MAIL_HOST=smtp.mailtrap.io -MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_ENCRYPTION=tls -MAIL_FROM_ADDRESS=noreply@example.com -MAIL_FROM_NAME="${APP_NAME}" - -# ------------------------------------------------------------------------------ -# Object Storage / Cloud Providers -# ------------------------------------------------------------------------------ -# AWS S3 / MinIO configuration -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -AWS_DEFAULT_REGION=us-east-1 -AWS_BUCKET= -AWS_USE_PATH_STYLE_ENDPOINT=false -# AWS_ENDPOINT=http://localhost:9000 # Uncomment for MinIO - -# Google Cloud Platform -# GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json -# GCP_PROJECT_ID= - -# ------------------------------------------------------------------------------ -# Observability / Logging / Telemetry -# ------------------------------------------------------------------------------ -# Log level: debug, info, warn, error, fatal -LOG_LEVEL=debug -LOG_FORMAT=json # or text -# Sentry DSN for error tracking -# SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 -# Datadog, Prometheus, NewRelic etc. -# DD_API_KEY= -# TELEMETRY_ENABLED=true - -# ------------------------------------------------------------------------------ -# Common Third-Party API Keys (Placeholders) -# ------------------------------------------------------------------------------ -# Stripe / Payment Gateway -# STRIPE_PUBLIC_KEY=pk_test_... -# STRIPE_SECRET_KEY=sk_test_... -# STRIPE_WEBHOOK_SECRET=whsec_... - -# AI / LLM APIs (OpenAI, Anthropic, etc.) -# OPENAI_API_KEY=sk-... -# ANTHROPIC_API_KEY=sk-ant-... - -# ------------------------------------------------------------------------------ -# Frontend / UI Specific Variables -# ------------------------------------------------------------------------------ -# Note: Many frontend frameworks require specific prefixes to expose vars to the client. -# The below variables show how to map backend values to frontend framework standards. -# Next.js: NEXT_PUBLIC_ -# Vite: VITE_ -# Create React App: REACT_APP_ -# Vue/Nuxt: NUXT_PUBLIC_ / VUE_APP_ - -NEXT_PUBLIC_APP_URL=${APP_URL} -NEXT_PUBLIC_API_URL=${APP_URL}/api/v1 -VITE_API_BASE_URL=${APP_URL}/api/v1 -REACT_APP_API_URL=${APP_URL}/api/v1 - -# ------------------------------------------------------------------------------ -# CI/CD & Deployment Flags -# ------------------------------------------------------------------------------ -# Flags to trigger specific build or deployment behaviors in GitHub Actions, GitLab CI, etc. -# CI=true -# SKIP_PREFLIGHT_CHECK=true -# DOCKER_BUILD_TARGET=development diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d386ce9..0f0af36 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,7 +34,7 @@ # ----------------------------------------------------------------------------- docs/ @markurtz *.md @markurtz -mkdocs.yml @markurtz +zensical.toml @markurtz # ----------------------------------------------------------------------------- # Security-Sensitive Files @@ -43,7 +43,6 @@ mkdocs.yml @markurtz # ----------------------------------------------------------------------------- SECURITY.md @markurtz .github/dependabot.yml @markurtz -.pre-commit-config.yaml @markurtz # ----------------------------------------------------------------------------- # Core Source Code diff --git a/.github/actions/oci/build/action.yml b/.github/actions/oci/build/action.yml new file mode 100644 index 0000000..68e32dd --- /dev/null +++ b/.github/actions/oci/build/action.yml @@ -0,0 +1,55 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "OCI Builder" +description: "Builds container image and optionally saves it as a tarball" +inputs: + save-image: + description: "Whether to save the built image to a tarball archive (for testing in later\ + \ jobs)" + required: false + default: "false" + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.oci_build == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Build OCI Image + if: steps.filter.outputs.oci_build == 'true' || inputs.force-run == 'true' + shell: bash + run: | + hatch run oci:build + - name: Save OCI Image to Tarball + if: (steps.filter.outputs.oci_build == 'true' || inputs.force-run == 'true') && inputs.save-image + == 'true' + shell: bash + run: |- + echo "[INFO] Serializing OCI image to tarball..." + docker save rustarium:latest -o rustarium-latest.tar diff --git a/.github/actions/oci/publish/action.yml b/.github/actions/oci/publish/action.yml new file mode 100644 index 0000000..67b8a16 --- /dev/null +++ b/.github/actions/oci/publish/action.yml @@ -0,0 +1,57 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "OCI Publisher" +description: "Tags and pushes container image to GHCR, optionally loading it from a tar archive" +inputs: + image-tag: + description: "The tag to use for pushing" + required: false + default: "latest" + github-token: + description: "GitHub token for authentication" + required: true + load-image: + description: "Whether to load the image from a tarball archive first" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Load OCI Image from Tarball + if: inputs.load-image == 'true' + shell: bash + run: | + if [ -f "rustarium-latest.tar" ]; then + echo "[INFO] Loading OCI image from rustarium-latest.tar..." + docker load -i rustarium-latest.tar + else + echo "[WARNING] rustarium-latest.tar not found! Proceeding without loading." + fi + - name: Log in to GHCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ inputs.github-token }} + - name: Tag and Push Image + shell: bash + env: + GITHUB_REPOSITORY: ${{ github.repository }} + IMAGE_TAG: ${{ inputs.image-tag }} + run: |- + docker tag rustarium:latest ghcr.io/"$GITHUB_REPOSITORY":"$IMAGE_TAG" + docker tag rustarium:latest ghcr.io/"$GITHUB_REPOSITORY":latest + docker push ghcr.io/"$GITHUB_REPOSITORY":"$IMAGE_TAG" + docker push ghcr.io/"$GITHUB_REPOSITORY":latest diff --git a/.github/actions/oci/quality/action.yml b/.github/actions/oci/quality/action.yml new file mode 100644 index 0000000..216e72c --- /dev/null +++ b/.github/actions/oci/quality/action.yml @@ -0,0 +1,52 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "OCI Quality" +description: "Runs Dockerfile and Docker Compose linters" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Install Hadolint + if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' + uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 + with: + tool: hadolint + - name: Install DCLint + if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' + shell: bash + run: npm install -g dclint@3.1.0 + - name: Run OCI Quality Checks + if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run oci:lint diff --git a/.github/actions/oci/security/action.yml b/.github/actions/oci/security/action.yml new file mode 100644 index 0000000..a07418a --- /dev/null +++ b/.github/actions/oci/security/action.yml @@ -0,0 +1,48 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "OCI Security" +description: "Runs dockle and trivy security scans on the container image" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.oci_security == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Install Dockle and Trivy + if: steps.filter.outputs.oci_security == 'true' || inputs.force-run == 'true' + uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 + with: + tool: dockle,trivy + - name: Run OCI Security Scan + if: steps.filter.outputs.oci_security == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run oci:security diff --git a/.github/actions/oci/tests/action.yml b/.github/actions/oci/tests/action.yml new file mode 100644 index 0000000..4c13a53 --- /dev/null +++ b/.github/actions/oci/tests/action.yml @@ -0,0 +1,78 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "OCI Tests" +description: "Runs container structure tests against the built image" +inputs: + test-level: + description: "Test level: unit, integration, e2e, or all" + required: false + default: "all" + test-category: + description: "Test category (not applicable for container structure tests)" + required: false + default: "all" + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.oci_tests == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Install container-structure-test + if: steps.filter.outputs.oci_tests == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi + if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then ARCH="arm64"; fi + curl -LO "https://github.com/GoogleContainerTools/container-structure-test/releases/download/v1.22.1/container-structure-test-${OS}-${ARCH}" + chmod +x "container-structure-test-${OS}-${ARCH}" + sudo mv "container-structure-test-${OS}-${ARCH}" /usr/local/bin/container-structure-test + - name: Run OCI Structure Tests + if: steps.filter.outputs.oci_tests == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + LEVEL="${{ inputs.test-level }}" + if [ "$LEVEL" = "e2e" ] || [ "$LEVEL" = "all" ]; then + # Optimize: check if image is already built/loaded by build job + if docker image inspect rustarium:latest >/dev/null 2>&1; then + echo "[INFO] OCI image rustarium:latest is already loaded. Running structure tests directly..." + hatch run -e oci python scripts/run_oci.py cstest + else + echo "[INFO] OCI image rustarium:latest not found. Building and running E2E tests..." + hatch run oci:tests-e2e + fi + else + if [ "$LEVEL" = "unit" ]; then + hatch run oci:tests-unit + elif [ "$LEVEL" = "integration" ]; then + hatch run oci:tests-int + fi + fi diff --git a/.github/actions/project/docs/action.yml b/.github/actions/project/docs/action.yml new file mode 100644 index 0000000..312cf13 --- /dev/null +++ b/.github/actions/project/docs/action.yml @@ -0,0 +1,153 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Project Docs Builder" +description: "Builds project documentation using Zensical and optionally deploys to GitHub\ + \ Pages" +inputs: + build-type: + description: "Deploy target type: dev-branch, dev, nightly, release, or post" + required: true + alias: + description: "The alias to use for the documentation version (e.g., latest, nightly)" + required: true + include-coverage: + description: "Whether to download and include unit test coverage in the docs" + required: false + default: "false" + deploy: + description: "Whether to commit and deploy the built docs to the gh-pages branch" + required: false + default: "false" + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +outputs: + deployed-url: + description: "Path where the docs were deployed" + value: ${{ steps.dest.outputs.path }} +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Configure Git Credentials + if: (steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true') && inputs.deploy + == 'true' + shell: bash + run: | + git config user.name github-actions[bot] + git config user.email github-actions[bot]@users.noreply.github.com + - name: Download Python test coverage + if: (steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true') && inputs.include-coverage + == 'true' + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + pattern: coverage-report-unit-python-py${{ inputs.python-version }}-* + path: coverage/python + merge-multiple: true + continue-on-error: true + - name: Download Rust test coverage + if: (steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true') && inputs.include-coverage + == 'true' + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + pattern: coverage-report-unit-rust-* + path: coverage/rust + merge-multiple: true + continue-on-error: true + - name: Compute deployment destination path + if: steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true' + id: dest + shell: bash + env: + BUILD_TYPE: ${{ inputs.build-type }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REF_NAME: ${{ github.ref_name }} + ALIAS: ${{ inputs.alias }} + run: | + case "$BUILD_TYPE" in + dev-branch) echo "path=pr-$PR_NUMBER" >> "$GITHUB_OUTPUT" ;; + dev) echo "path=dev" >> "$GITHUB_OUTPUT" ;; + nightly) echo "path=nightly" >> "$GITHUB_OUTPUT" ;; + release) echo "path=$REF_NAME" >> "$GITHUB_OUTPUT" ;; + post) echo "path=post" >> "$GITHUB_OUTPUT" ;; + *) echo "path=$ALIAS" >> "$GITHUB_OUTPUT" ;; + esac + - name: Build and deploy using Git + if: steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true' + shell: bash + env: + VERSION_PATH: ${{ steps.dest.outputs.path }} + ALIAS: ${{ inputs.alias }} + BUILD_TYPE: ${{ inputs.build-type }} + DEPLOY: ${{ inputs.deploy }} + run: |- + # Build documentation + hatch run project:docs + if [ "$DEPLOY" = "true" ]; then + # Save built site files outside repo + mv site ../site-temp + + # Checkout gh-pages branch + git fetch origin gh-pages:gh-pages || true + git checkout gh-pages || git checkout --orphan gh-pages + + # Clean target directory and copy new files + rm -rf "$VERSION_PATH" + mkdir -p "$VERSION_PATH" + cp -r ../site-temp/* "$VERSION_PATH"/ + + # Deploy to alias path if alias is configured + if [ "$ALIAS" != "" ] && [ "$ALIAS" != "$VERSION_PATH" ]; then + rm -rf "$ALIAS" + mkdir -p "$ALIAS" + cp -r ../site-temp/* "$ALIAS"/ + fi + + # Set default redirect if release build + if [ "$BUILD_TYPE" == "release" ]; then + printf '%s\n' \ + '' \ + '' \ + '' \ + " " \ + ' ' \ + '' \ + '' \ + "

Redirecting to latest documentation...

" \ + '' \ + '' > index.html + fi + + # Commit changes and push + git add -A + git commit -m "docs: deploy documentation for $VERSION_PATH" || echo "No changes to commit" + git push origin gh-pages + fi diff --git a/.github/actions/project/publish/action.yml b/.github/actions/project/publish/action.yml new file mode 100644 index 0000000..44e3319 --- /dev/null +++ b/.github/actions/project/publish/action.yml @@ -0,0 +1,23 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Project Publish" +description: "Placeholder action (publishing is handled at component level)" +runs: + using: "composite" + steps: + - name: Project Publish Info + shell: bash + run: |- + echo "[INFO] Project-level publishing is handled via individual component actions (Python, OCI)." diff --git a/.github/actions/project/quality/action.yml b/.github/actions/project/quality/action.yml new file mode 100644 index 0000000..25cf6d1 --- /dev/null +++ b/.github/actions/project/quality/action.yml @@ -0,0 +1,43 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Project Quality" +description: "Runs project linting and formatting checks (mdformat, yamlfix, yamllint, taplo)" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.project_quality == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Run Project Quality Checks + if: steps.filter.outputs.project_quality == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run project:lint diff --git a/.github/actions/project/security/action.yml b/.github/actions/project/security/action.yml new file mode 100644 index 0000000..8463252 --- /dev/null +++ b/.github/actions/project/security/action.yml @@ -0,0 +1,50 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Project Security" +description: "Runs project security checks (detect-secrets, checkov, actionlint, dependency-review)" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Run Project Security Checks + if: steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run project:security + - name: Lint GitHub Actions Workflows + if: steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true' + uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0 + - name: Dependency Review + if: (steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true') + && github.event_name == 'pull_request' + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/actions/project/tests/action.yml b/.github/actions/project/tests/action.yml new file mode 100644 index 0000000..627a518 --- /dev/null +++ b/.github/actions/project/tests/action.yml @@ -0,0 +1,94 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Project Tests" +description: "Runs project tests (markdown link check, phmdoctest) or end-to-end (e2e) tests" +inputs: + test-level: + description: "Test level: unit, integration, e2e, or all" + required: false + default: "all" + test-category: + description: "Test category: smoke, sanity, regression, or all (applies to doc tests)" + required: false + default: "all" + extra-args: + description: "Extra arguments to pass to the tests" + required: false + default: "" + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.project_tests == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Run Project Tests + if: steps.filter.outputs.project_tests == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + LEVEL="${{ inputs.test-level }}" + CATEGORY="${{ inputs.test-category }}" + EXTRA="${{ inputs.extra-args }}" + ARGS="$EXTRA" + if [ "$CATEGORY" != "all" ] && [ -n "$CATEGORY" ]; then + ARGS="-m $CATEGORY $EXTRA" + fi + run_unit() { + echo "[INFO] No unit tests are defined for the project environment." + } + run_int() { + echo "[INFO] Running Project integration tests (doc tests)..." + # Bypasses direct pyproject.toml arguments collision + hatch run -e project python scripts/generate_doc_tests.py + hatch run -e python pytest .tests/docs $ARGS + } + run_e2e() { + echo "[INFO] Running Project E2E tests (link checking)..." + hatch run project:tests-e2e $EXTRA + } + case "$LEVEL" in + unit) + run_unit + ;; + integration) + run_int + ;; + e2e) + run_e2e + ;; + all) + run_unit + run_int + run_e2e + ;; + *) + echo "Unknown test level: $LEVEL" + exit 1 + ;; + esac diff --git a/.github/actions/python/build/action.yml b/.github/actions/python/build/action.yml new file mode 100644 index 0000000..c028932 --- /dev/null +++ b/.github/actions/python/build/action.yml @@ -0,0 +1,47 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Python Build" +description: "Builds the python wheel and sdist package using Hatch" +inputs: + python-version: + description: "Python version to build package against" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +outputs: + location: + description: "Directory where artifacts are built" + value: "dist/" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.python_build == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Build Package + if: steps.filter.outputs.python_build == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch build diff --git a/.github/actions/python/publish/action.yml b/.github/actions/python/publish/action.yml new file mode 100644 index 0000000..97a345b --- /dev/null +++ b/.github/actions/python/publish/action.yml @@ -0,0 +1,61 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Python Publish" +description: "Downloads python package build, attests build provenance, publishes to PyPI,\ + \ and optionally creates GitHub Release" +inputs: + artifact-name: + description: "The name of the build artifact to download" + required: false + default: "" + create-release: + description: "Whether to create a GitHub Release (requires tags)" + required: false + default: "false" + github-token: + description: "GitHub token for Release creation" + required: false + default: "" + pypi-token: + description: "Optional PyPI token (not needed if trusted publishing OIDC is configured)" + required: false + default: "" +runs: + using: "composite" + steps: + - name: Download build artifact + if: inputs.artifact-name != '' + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: ${{ inputs.artifact-name }} + path: dist/ + - name: Generate artifact attestations + uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 + with: + subject-path: dist/* + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + with: + packages-dir: dist/ + password: ${{ inputs.pypi-token }} + - name: Create GitHub Release + if: inputs.create-release == 'true' && startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 + with: + files: dist/* + generate_release_notes: true + prerelease: false + env: + GITHUB_TOKEN: ${{ inputs.github-token }} diff --git a/.github/actions/python/quality/action.yml b/.github/actions/python/quality/action.yml new file mode 100644 index 0000000..3e99121 --- /dev/null +++ b/.github/actions/python/quality/action.yml @@ -0,0 +1,44 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Python Quality" +description: "Runs python lint and types check" +inputs: + python-version: + description: "Python version to run checks against" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.python_quality == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Run Python Lint & Types + if: steps.filter.outputs.python_quality == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run python:lint + hatch run python:types diff --git a/.github/actions/python/security/action.yml b/.github/actions/python/security/action.yml new file mode 100644 index 0000000..be419ff --- /dev/null +++ b/.github/actions/python/security/action.yml @@ -0,0 +1,43 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Python Security" +description: "Runs python security scan (semgrep, pip-audit, ruff S)" +inputs: + python-version: + description: "Python version to run checks against" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.python_security == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Run Python Security Audit + if: steps.filter.outputs.python_security == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run python:security diff --git a/.github/actions/python/tests/action.yml b/.github/actions/python/tests/action.yml new file mode 100644 index 0000000..c71bfaf --- /dev/null +++ b/.github/actions/python/tests/action.yml @@ -0,0 +1,128 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Python Tests" +description: "Runs python unit, integration, and e2e tests with optional coverage reporting" +inputs: + python-version: + description: "Python version to run checks against" + required: false + default: "3.10" + test-level: + description: "Test level: unit, integration, e2e, or all" + required: false + default: "all" + test-category: + description: "Test category: smoke, sanity, regression, or all" + required: false + default: "all" + generate-coverage: + description: "Generate test coverage report" + required: false + default: "false" + extra-args: + description: "Extra arguments to pass to pytest" + required: false + default: "" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Python & Hatch + if: steps.filter.outputs.python_tests == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} + - name: Run Python Tests + if: steps.filter.outputs.python_tests == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + LEVEL="${{ inputs.test-level }}" + CATEGORY="${{ inputs.test-category }}" + COV="${{ inputs.generate-coverage }}" + EXTRA="${{ inputs.extra-args }}" + + # Construct pytest marker arguments if category is specified + ARGS="$EXTRA" + if [ "$CATEGORY" != "all" ] && [ -n "$CATEGORY" ]; then + ARGS="-m $CATEGORY $EXTRA" + fi + run_unit() { + echo "[INFO] Running Python unit tests..." + if [ "$COV" = "true" ]; then + hatch run python:tests-unit-cov $ARGS + else + hatch run python:tests-unit $ARGS + fi + } + run_int() { + echo "[INFO] Running Python integration tests..." + if [ "$COV" = "true" ]; then + hatch run python:tests-int-cov $ARGS + else + hatch run python:tests-int $ARGS + fi + } + run_e2e() { + # Check if a pre-built wheel is present in dist/ + if [ -d "dist" ] && [ "$(find dist -name '*.whl' | wc -l)" -gt 0 ]; then + echo "[INFO] Found pre-built wheel in dist/. Installing and running E2E tests..." + hatch run -e python pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium + if [ "$COV" = "true" ]; then + hatch run -e python pytest --cov=rustarium --cov-report=term --cov-report=markdown:coverage/python/coverage_tests-e2e.md tests/e2e $ARGS + else + hatch run -e python pytest tests/e2e $ARGS + fi + else + echo "[INFO] No pre-built wheel found in dist/. Building and running E2E tests locally..." + if [ "$COV" = "true" ]; then + hatch run python:tests-e2e-cov $ARGS + else + hatch run python:tests-e2e $ARGS + fi + fi + } + case "$LEVEL" in + unit) + run_unit + ;; + integration) + run_int + ;; + e2e) + run_e2e + ;; + all) + # Run functional (unit + integration) + if [ "$COV" = "true" ]; then + hatch run python:tests-func-cov $ARGS + else + hatch run python:tests-func $ARGS + fi + # Run E2E + run_e2e + ;; + *) + echo "Unknown test level: $LEVEL" + exit 1 + ;; + esac diff --git a/.github/actions/rust/build/action.yml b/.github/actions/rust/build/action.yml new file mode 100644 index 0000000..8acf8a3 --- /dev/null +++ b/.github/actions/rust/build/action.yml @@ -0,0 +1,43 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Rust Build" +description: "Builds the cargo workspace to verify compilation" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Rust Environment + if: steps.filter.outputs.rust_build == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-rust + with: + python-version: ${{ inputs.python-version }} + - name: Build Rust Workspace + if: steps.filter.outputs.rust_build == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + cargo build --workspace --all-targets diff --git a/.github/actions/rust/publish/action.yml b/.github/actions/rust/publish/action.yml new file mode 100644 index 0000000..54949cf --- /dev/null +++ b/.github/actions/rust/publish/action.yml @@ -0,0 +1,24 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Rust Publish" +description: "Placeholder action (Rust bindings are published inside python wheels)" +runs: + using: "composite" + steps: + - name: Rust Publish Info + shell: bash + run: |- + echo "[INFO] Rust bindings publication is managed via the python package wheel distribution." + echo "[INFO] See python/publish action for publishing to PyPI." diff --git a/.github/actions/rust/quality/action.yml b/.github/actions/rust/quality/action.yml new file mode 100644 index 0000000..68328d5 --- /dev/null +++ b/.github/actions/rust/quality/action.yml @@ -0,0 +1,45 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Rust Quality" +description: "Runs cargo fmt and clippy" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Rust Environment + if: steps.filter.outputs.rust_quality == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-rust + with: + components: rustfmt, clippy + python-version: ${{ inputs.python-version }} + - name: Run Rust Lint & Types + if: steps.filter.outputs.rust_quality == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run rust:lint + hatch run rust:types diff --git a/.github/actions/rust/security/action.yml b/.github/actions/rust/security/action.yml new file mode 100644 index 0000000..13fd36c --- /dev/null +++ b/.github/actions/rust/security/action.yml @@ -0,0 +1,48 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Rust Security" +description: "Runs cargo audit and cargo deny check" +inputs: + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Rust Environment + if: steps.filter.outputs.rust_security == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-rust + with: + python-version: ${{ inputs.python-version }} + - name: Install cargo-deny and cargo-audit + if: steps.filter.outputs.rust_security == 'true' || inputs.force-run == 'true' + uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 + with: + tool: cargo-deny,cargo-audit + - name: Run Rust Security Audit + if: steps.filter.outputs.rust_security == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + hatch run rust:security diff --git a/.github/actions/rust/tests/action.yml b/.github/actions/rust/tests/action.yml new file mode 100644 index 0000000..ba9eb6f --- /dev/null +++ b/.github/actions/rust/tests/action.yml @@ -0,0 +1,121 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Rust Tests" +description: "Runs cargo tests with optional llvm-cov coverage reporting" +inputs: + test-level: + description: "Test level: unit, integration, e2e, or all" + required: false + default: "all" + test-category: + description: "Test category: smoke, sanity, regression, or all (matches test name substring)" + required: false + default: "all" + generate-coverage: + description: "Generate test coverage report" + required: false + default: "false" + extra-args: + description: "Extra arguments to pass to Cargo" + required: false + default: "" + python-version: + description: "Python version to set up" + required: false + default: "3.10" + force-run: + description: "Always run even if no changes are detected" + required: false + default: "false" +runs: + using: "composite" + steps: + - name: Detect changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + - name: Set up Rust Environment + if: steps.filter.outputs.rust_tests == 'true' || inputs.force-run == 'true' + uses: ./.github/actions/utility/setup-rust + with: + python-version: ${{ inputs.python-version }} + - name: Install cargo-llvm-cov + if: (steps.filter.outputs.rust_tests == 'true' || inputs.force-run == 'true') && inputs.generate-coverage + == 'true' + uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 + with: + tool: cargo-llvm-cov + - name: Run Rust Tests + if: steps.filter.outputs.rust_tests == 'true' || inputs.force-run == 'true' + shell: bash + run: |- + LEVEL="${{ inputs.test-level }}" + CATEGORY="${{ inputs.test-category }}" + COV="${{ inputs.generate-coverage }}" + EXTRA="${{ inputs.extra-args }}" + + # If a category is specified, use it as a test name filter for cargo test + ARGS="$EXTRA" + if [ "$CATEGORY" != "all" ] && [ -n "$CATEGORY" ]; then + ARGS="-- $CATEGORY $EXTRA" + fi + run_unit() { + echo "[INFO] Running Rust unit tests..." + if [ "$COV" = "true" ]; then + hatch run rust:tests-unit-cov $ARGS + else + hatch run rust:tests-unit $ARGS + fi + } + run_int() { + echo "[INFO] Running Rust integration tests..." + if [ "$COV" = "true" ]; then + hatch run rust:tests-int-cov $ARGS + else + hatch run rust:tests-int $ARGS + fi + } + run_e2e() { + echo "[INFO] Running Rust E2E tests..." + if [ "$COV" = "true" ]; then + hatch run rust:tests-e2e-cov $ARGS + else + hatch run rust:tests-e2e $ARGS + fi + } + case "$LEVEL" in + unit) + run_unit + ;; + integration) + run_int + ;; + e2e) + run_e2e + ;; + all) + echo "[INFO] Running all Rust tests..." + if [ "$COV" = "true" ]; then + hatch run rust:tests-func-cov $ARGS + else + hatch run rust:tests-func $ARGS + fi + ;; + *) + echo "Unknown test level: $LEVEL" + exit 1 + ;; + esac diff --git a/.github/actions/utility/setup-python/action.yml b/.github/actions/utility/setup-python/action.yml new file mode 100644 index 0000000..5308b49 --- /dev/null +++ b/.github/actions/utility/setup-python/action.yml @@ -0,0 +1,32 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Setup Python Environment" +description: "Utility action to set up Python, and install uv and Hatch" +inputs: + python-version: + description: "Python version to set up" + required: true +runs: + using: "composite" + steps: + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: ${{ inputs.python-version }} + - name: Install uv and Hatch + shell: bash + run: |- + pip install --break-system-packages --ignore-installed uv>=0.11.0 # Pin to latest stable/compatible uv + pip install --break-system-packages --ignore-installed hatch>=1.16.0 # Pin to latest stable/compatible hatch diff --git a/.github/actions/utility/setup-rust/action.yml b/.github/actions/utility/setup-rust/action.yml new file mode 100644 index 0000000..5272ece --- /dev/null +++ b/.github/actions/utility/setup-rust/action.yml @@ -0,0 +1,37 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Setup Rust Environment" +description: "Utility action to set up Rust toolchain, Python environment, and Hatch orchestrator" +inputs: + components: + description: "Rust components to install (comma-separated)" + required: false + default: "" + python-version: + description: "Python version to set up" + required: false + default: "3.10" +runs: + using: "composite" + steps: + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1 + with: + components: ${{ inputs.components }} + cache: false + - name: Set up Python & Hatch + uses: ./.github/actions/utility/setup-python + with: + python-version: ${{ inputs.python-version }} diff --git a/.github/paths-filter.yml b/.github/paths-filter.yml new file mode 100644 index 0000000..a65d519 --- /dev/null +++ b/.github/paths-filter.yml @@ -0,0 +1,170 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +# Rustarium Centralized Path Filters +# ============================================================================== +# This file centralizes path filters for change detection in workflows +# and custom actions (evaluated by dorny/paths-filter). +# +# Shared CI/CD Constants (Documented for reference across actions/workflows): +# - Default Python Version: "3.10" +# - Matrix Python Versions (Dev/Main): ["3.10", "3.13"] +# - Matrix Python Versions (Nightly): ["3.10", "3.11", "3.12", "3.13"] +# - Checkout Action Version: "actions/checkout@v4" (v4.2.2 - 11bd71901bbe5b1630ceea73d27597364c9af683) +# ============================================================================== +# ── Change Detection Filters ────────────────────────────────────────────────── +# Evaluated by dorny/paths-filter. Keys correspond to the specific workflow stages. +docs: + - 'docs/**' + - 'zensical.toml' +python_quality: + - 'src/**' + - 'tests/**' + - 'examples/**' + - 'scripts/**' + - 'pyproject.toml' + - 'uv.lock' + - 'Cargo.toml' + - 'Cargo.lock' + - 'crates/**' + - '.github/actions/python/quality/**' +python_security: + - 'src/**' + - 'tests/**' + - 'examples/**' + - 'scripts/**' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/python/security/**' +python_tests: + - 'src/**' + - 'tests/**' + - 'examples/**' + - 'scripts/**' + - 'pyproject.toml' + - 'uv.lock' + - 'Cargo.toml' + - 'Cargo.lock' + - 'crates/**' + - '.github/actions/python/tests/**' +python_build: + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/python/build/**' +rust_quality: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/rust/quality/**' +rust_security: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/rust/security/**' +rust_tests: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/rust/tests/**' +rust_build: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/rust/build/**' +oci_quality: + - 'Dockerfile' + - 'docker-compose.yml' + - 'cst.yaml' + - 'pyproject.toml' + - 'uv.lock' + - '.github/**' +oci_security: + - 'Dockerfile' + - 'docker-compose.yml' + - 'cst.yaml' + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/**' +oci_tests: + - 'Dockerfile' + - 'docker-compose.yml' + - 'cst.yaml' + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/**' +oci_build: + - 'Dockerfile' + - 'docker-compose.yml' + - 'cst.yaml' + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'pyproject.toml' + - 'uv.lock' + - '.github/**' +project_quality: + - '**/*.md' + - '**/*.yaml' + - '**/*.yml' + - '**/*.toml' + - 'crates/**' + - 'docs/**' + - 'examples/**' + - 'scripts/**' + - 'src/**' + - 'tests/**' + - '.github/actions/project/quality/**' +project_security: + - '**' +project_tests: + - '**/*.md' + - 'crates/**' + - 'docs/**' + - 'examples/**' + - 'scripts/**' + - 'src/**' + - 'tests/**' + - 'pyproject.toml' + - 'uv.lock' + - '.github/actions/project/tests/**' +project_docs: + - 'docs/**' + - 'zensical.toml' + - 'pyproject.toml' + - 'uv.lock' + - 'src/**' + - 'crates/**' + - '.github/actions/project/docs/**' diff --git a/.github/workflows/_build_container.yml b/.github/workflows/_build_container.yml deleted file mode 100644 index f19770a..0000000 --- a/.github/workflows/_build_container.yml +++ /dev/null @@ -1,75 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _build_container.yml -# -# Builds container images for the project. This workflow handles only the -# build step. It returns the image name and tag, allowing calling workflows -# to handle pushing, publishing, or vulnerability scanning. -# -# Inputs: -# build_type (required): "release", "post", "nightly", or "dev" -# push (optional): Whether to push the image to GHCR (default: false) -# -# Outputs: -# image_name — The name of the built container image -# image_tag — The tag of the built container image -# -# Callers: development.yml, nightly.yml, release.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Build Container (Reusable)" -on: - workflow_call: - inputs: - build_type: - description: "Type of build: release, post, nightly, or dev" - required: true - type: string - push: - description: "Whether to push the built container image to GHCR" - required: false - type: boolean - default: false - outputs: - image_name: - description: "The name of the built container image" - value: ${{ jobs.build-container.outputs.image_name }} - image_tag: - description: "The tag of the built container image" - value: ${{ jobs.build-container.outputs.image_tag }} -permissions: - contents: read - packages: write # To push to GHCR -jobs: - build-container: - name: "Build Container (${{ inputs.build_type }})" - runs-on: ubuntu-latest - outputs: - image_name: ${{ steps.build.outputs.image_name }} - image_tag: ${{ steps.build.outputs.image_tag }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - - name: Log in to GitHub Container Registry - if: ${{ inputs.push }} - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push container - uses: docker/build-push-action@2cdde99a3119bfeb512642f4c2201b17b620eeff # v5 - with: - context: . - push: ${{ inputs.push }} - load: ${{ !inputs.push }} - tags: ghcr.io/${{ github.repository }}:${{ inputs.build_type }}-${{ github.sha }},ghcr.io/${{ - github.repository }}:latest - cache-from: type=gha - cache-to: type=gha,mode=max - - name: Export outputs - id: build - run: |- - echo "image_name=ghcr.io/${{ github.repository }}" >> "$GITHUB_OUTPUT" - echo "image_tag=${{ inputs.build_type }}-${{ github.sha }}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/_build_package.yml b/.github/workflows/_build_package.yml deleted file mode 100644 index 95c0944..0000000 --- a/.github/workflows/_build_package.yml +++ /dev/null @@ -1,66 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _build_package.yml -# -# Compiles or packages the project. This workflow handles only the build step. -# It returns the directory where the build artifacts are located, allowing -# calling workflows to handle uploading, publishing, or further processing. -# -# Inputs: -# build_type (required): "release", "post", "nightly", or "dev" -# -# Outputs: -# build_location — Path to the directory containing the build output -# build_metadata — Any important metadata about the build (e.g., version, sha256) -# -# Callers: development.yml, nightly.yml, release.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Build Package (Reusable)" -on: - workflow_call: - inputs: - build_type: - description: "Type of build: release, post, nightly, or dev" - required: true - type: string - outputs: - build_location: - description: "Directory path containing the built artifacts" - value: ${{ jobs.build-package.outputs.location }} - build_metadata: - description: "Metadata regarding the build (e.g. SHA256)" - value: ${{ jobs.build-package.outputs.metadata }} -permissions: - contents: read -jobs: - build-package: - name: "Build Package (${{ inputs.build_type }})" - runs-on: ubuntu-latest - outputs: - location: ${{ steps.build.outputs.location }} - metadata: ${{ steps.build.outputs.metadata }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: "Set up build toolchain" - uses: pypa/hatch@install - - name: "Build package" - id: build - run: | - hatch build - - # Output the location - echo "location=dist/" >> "$GITHUB_OUTPUT" - - # Compute SHA256 or other metadata - SHA=$(find dist/ -type f | sort | xargs sha256sum | sha256sum | awk '{print $1}') - echo "metadata=${SHA}" >> "$GITHUB_OUTPUT" - - name: Upload build artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: build-artifact-${{ inputs.build_type }}-${{ github.run_id }} - path: dist/ - retention-days: 7 - if-no-files-found: warn diff --git a/.github/workflows/_docs.yml b/.github/workflows/_docs.yml deleted file mode 100644 index 7fa1cfa..0000000 --- a/.github/workflows/_docs.yml +++ /dev/null @@ -1,112 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _docs.yml -# -# Builds static documentation using MkDocs and Mike for versioning, and -# deploys it to GitHub Pages. -# -# Inputs: -# build_type (required): "dev-branch", "dev", "nightly", "release", or "post" -# alias (required): The alias to use for the deployment (e.g., "latest", "nightly") -# -# Outputs: -# deployed_url — The URL/path where the docs were deployed -# -# Callers: development.yml, nightly.yml, release.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Docs Builder & Deployer (Reusable)" -on: - workflow_call: - inputs: - build_type: - description: "Deploy target type: dev-branch, dev, nightly, release, or post" - required: true - type: string - alias: - description: "The alias to use for the documentation version (e.g., latest, nightly)" - required: true - type: string - include_coverage: - description: "Whether to download and include unit test coverage in the docs" - required: false - type: boolean - default: false - outputs: - deployed_url: - description: "Path where the docs were deployed" - value: ${{ jobs.build-and-deploy-docs.outputs.deployed_url }} -permissions: - contents: write # Push to gh-pages branch -jobs: - build-and-deploy-docs: - name: "Build & Deploy Docs (${{ inputs.build_type }})" - runs-on: ubuntu-latest - outputs: - deployed_url: ${{ steps.dest.outputs.path }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.x" - - name: Configure Git Credentials - run: | - git config user.name github-actions[bot] - git config user.email github-actions[bot]@users.noreply.github.com - - name: Install hatch and trigger docs sync - run: | - pip install uv hatch - hatch run docs:mkdocs --version - - name: Download unit test coverage - if: ${{ inputs.include_coverage }} - uses: actions/download-artifact@v4 - with: - pattern: coverage-report-unit-* - path: docs/coverage/unit - merge-multiple: true - - name: Download integration test coverage - if: ${{ inputs.include_coverage }} - uses: actions/download-artifact@v4 - with: - pattern: coverage-report-integration-* - path: docs/coverage/integration - merge-multiple: true - - name: Download e2e test coverage - if: ${{ inputs.include_coverage }} - uses: actions/download-artifact@v4 - with: - pattern: coverage-report-e2e-* - path: docs/coverage/e2e - merge-multiple: true - - name: Compute deployment destination path - id: dest - run: | - case "${{ inputs.build_type }}" in - dev-branch) echo "path=pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" ;; - dev) echo "path=dev" >> "$GITHUB_OUTPUT" ;; - nightly) echo "path=nightly" >> "$GITHUB_OUTPUT" ;; - release) echo "path=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" ;; - post) echo "path=post" >> "$GITHUB_OUTPUT" ;; - *) echo "path=${{ inputs.alias }}" >> "$GITHUB_OUTPUT" ;; - esac - - name: Build and deploy using Mike - run: |- - VERSION_PATH="${{ steps.dest.outputs.path }}" - ALIAS="${{ inputs.alias }}" - - # Use mike to deploy to gh-pages branch - # --push: push to origin - # --update-aliases: update the alias pointer - if [ "$ALIAS" != "" ] && [ "$ALIAS" != "$VERSION_PATH" ]; then - hatch run docs:mike deploy --push --update-aliases $VERSION_PATH $ALIAS - else - hatch run docs:mike deploy --push $VERSION_PATH - fi - - # If release, also set it as default - if [ "${{ inputs.build_type }}" == "release" ]; then - hatch run docs:mike set-default --push $ALIAS - fi diff --git a/.github/workflows/_link-check.yml b/.github/workflows/_link-check.yml deleted file mode 100644 index 2ea4ea3..0000000 --- a/.github/workflows/_link-check.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _link-check.yml -# -# Validates that all hyperlinks in project documentation and Markdown files -# are reachable and not returning errors. -# -# Inputs: -# fail_on_error — whether to fail the workflow on broken links (default: true) -# -# Outputs: -# report_content — The content of the link check report. -# -# Callers: development.yml, nightly.yml, release.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Link Checker (Reusable)" -on: - workflow_call: - inputs: - fail_on_error: - description: "Fail the workflow if broken links are detected" - required: false - type: boolean - default: true - outputs: - report_content: - description: "The contents of the link check report" - value: ${{ jobs.link-check.outputs.report_content }} -permissions: - contents: read -jobs: - link-check: - name: "Check Links" - runs-on: ubuntu-latest - outputs: - report_content: ${{ steps.read-report.outputs.content }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Run lychee link checker - uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 - id: lychee - with: - args: --verbose --no-progress --accept 200,206,301,302,429 --exclude-loopback --exclude - "\{\{[^}]+\}\}" '**/*.md' - fail: ${{ inputs.fail_on_error }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Read report content - id: read-report - if: always() - run: |- - if [ -f "lychee/out.md" ]; then - # Read file into output variable safely - EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - echo "content<<$EOF" >> "$GITHUB_OUTPUT" - cat lychee/out.md >> "$GITHUB_OUTPUT" - echo "$EOF" >> "$GITHUB_OUTPUT" - else - echo "content=No link errors found." >> "$GITHUB_OUTPUT" - fi diff --git a/.github/workflows/_pr_comment.yml b/.github/workflows/_pr_comment.yml deleted file mode 100644 index aa813ba..0000000 --- a/.github/workflows/_pr_comment.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -# ============================================================================= -# pr_comment.yml -# -# Triggers: -# workflow_run → CI — Development (completed) -# -# Resolves the GitHub Actions fork permission issue. Workflows triggered -# via `pull_request` from a fork do not have `write` permissions to post -# comments. `workflow_run` executes in the context of the base repository. -# ============================================================================= -name: "PR Commenter" -on: - workflow_run: - workflows: - - "CI — Development" - types: - - completed -permissions: - pull-requests: write - actions: read -jobs: - post_comment: - name: "Post PR Status Comment" - runs-on: ubuntu-latest - if: > - github.event.workflow_run.event == 'pull_request' - steps: - - name: "Download PR Number Artifact" - # Wait, how does it know the PR number? The `development.yml` needs to upload the PR number as an artifact - # so this workflow can read it. Let's write the script to find the PR associated with the commit. - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 - with: - script: |- - const workflowRun = context.payload.workflow_run; - // Get the pull request associated with the commit - const response = await github.rest.search.issuesAndPullRequests({ - q: `is:pr repo:${context.repo.owner}/${context.repo.repo} sha:${workflowRun.head_sha}` - }); - if (response.data.total_count === 0) { - console.log('No PR found for this commit.'); - return; - } - const prNumber = response.data.items[0].number; - const runUrl = workflowRun.html_url; - const status = workflowRun.conclusion; - let body = `## CI Development Pipeline Status\n\n`; - if (status === 'success') { - body += `✅ **Pipeline**: Completed successfully. [View Run Details](${runUrl})\n`; - } else { - body += `❌ **Pipeline**: Failed or was cancelled. [Check logs](${runUrl})\n`; - } - // Post comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: body - }); diff --git a/.github/workflows/_quality.yml b/.github/workflows/_quality.yml deleted file mode 100644 index 4fa5411..0000000 --- a/.github/workflows/_quality.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _quality.yml -# -# Encapsulates quality checking, style/lint, and type checking. -# Fails the pipeline if any of these checks fail. -# -# Callers: development.yml, main.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Quality Gate (Reusable)" -on: - workflow_call: - inputs: - python_versions: - description: "JSON array of Python versions" - required: true - type: string -permissions: - contents: read - security-events: write -jobs: - quality: - name: "Quality (Python ${{ matrix.python-version }})" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ${{ fromJson(inputs.python_versions) }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: "Set up Python ${{ matrix.python-version }}" - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ matrix.python-version }} - - name: "Install uv" - run: pip install uv - - name: "Set up toolchain" - uses: pypa/hatch@install - - name: "Run quality checks" - run: | - hatch run lint:pre-commit - types: - name: "Types (Python ${{ matrix.python-version }})" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ${{ fromJson(inputs.python_versions) }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: "Set up Python ${{ matrix.python-version }}" - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ matrix.python-version }} - - name: "Install uv" - run: pip install uv - - name: "Set up toolchain" - uses: pypa/hatch@install - - name: "Run type checks" - run: |- - hatch run types:check diff --git a/.github/workflows/_security.yml b/.github/workflows/_security.yml deleted file mode 100644 index e4c5404..0000000 --- a/.github/workflows/_security.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _security.yml -# -# Performs dependency vulnerability scanning and secret scanning. -# -# Callers: development.yml, main.yml, nightly.yml, release.yml, weekly.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Security Audit (Reusable)" -on: - workflow_call: -permissions: - contents: read - security-events: write # Upload SARIF to GitHub Security tab -jobs: - secret-scan: - name: "Secret Scan" - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: Run TruffleHog - uses: trufflesecurity/trufflehog@8a12e8e2fb6f3c4a4294a8e63b3659af6c08cfe3 # main - with: - path: ./ - dependency-audit: - name: "Dependency Vulnerability Scan" - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Set up toolchain - run: pip install pip-audit hatch - - name: Run dependency vulnerability scan - run: | - # Use hatch to export dependencies and run pip-audit - hatch dep show requirements > requirements.txt - pip-audit -r requirements.txt --output json -o audit.json - - name: Upload SCA results - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: sca-results-${{ github.run_id }} - path: audit.json - retention-days: 30 - if-no-files-found: ignore diff --git a/.github/workflows/_tests.yml b/.github/workflows/_tests.yml deleted file mode 100644 index 7e1592f..0000000 --- a/.github/workflows/_tests.yml +++ /dev/null @@ -1,123 +0,0 @@ ---- -# ───────────────────────────────────────────────────────────────────────────── -# _tests.yml -# -# Executes the project test suite using specified types and levels. -# -# Inputs: -# test_types (required) — Comma-separated list: smoke, sanity, regression -# test_levels (required) — Comma-separated list: unit, integration, e2e -# generate_coverage (optional) — Generate test coverage report (default: false) -# publish_results (optional) — Upload test results/coverage (default: true) -# retention_days (optional) — Days to retain artifacts (default: 7) -# -# Outputs: -# results_location — Path to the uploaded test results artifact -# coverage_location — Path to the uploaded coverage artifact -# -# Callers: development.yml, main.yml, nightly.yml, release.yml, weekly.yml -# ───────────────────────────────────────────────────────────────────────────── -name: "Test Runner (Reusable)" -on: - workflow_call: - inputs: - test_matrix: - description: "JSON array of test levels and types (e.g., '[{\"level\": \"unit\", \"\ - types\": \"smoke, sanity\"}]')" - required: true - type: string - python_versions: - description: "JSON array of Python versions" - required: true - type: string - generate_coverage: - description: "Generate test coverage report" - required: false - type: boolean - default: false - publish_results: - description: "Publish test results as artifacts" - required: false - type: boolean - default: true - retention_days: - description: "Days to retain test artifacts" - required: false - type: number - default: 7 - outputs: - results_location: - description: "Path to the test results artifact" - value: ${{ jobs.run-tests.outputs.results_location }} - coverage_location: - description: "Path to the coverage artifact" - value: ${{ jobs.run-tests.outputs.coverage_location }} -permissions: - contents: read -jobs: - run-tests: - name: "Run ${{ matrix.test-config.level }} Tests (Python ${{ matrix.python-version }})" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ${{ fromJson(inputs.python_versions) }} - test-config: ${{ fromJson(inputs.test_matrix) }} - outputs: - results_location: "reports/" - coverage_location: "coverage/" - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: "Set up Python ${{ matrix.python-version }}" - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ matrix.python-version }} - - name: "Install uv" - run: pip install uv - - name: "Set up toolchain" - uses: pypa/hatch@install - - name: "Run tests" - run: | - # ─────────────────────────────────────────────────────────────────── - # Level: ${{ matrix.test-config.level }} - # Types: ${{ matrix.test-config.types }} - # Coverage: ${{ inputs.generate_coverage }} / ${{ matrix.test-config.coverage }} - # ─────────────────────────────────────────────────────────────────── - TYPES="${{ matrix.test-config.types }}" - MARKERS=$(echo "$TYPES" | sed 's/, / or /g' | sed 's/,/ or /g') - GENERATE_COVERAGE="false" - if [ "${{ inputs.generate_coverage }}" == "true" ] || [ "${{ matrix.test-config.coverage }}" == "true" ]; then - GENERATE_COVERAGE="true" - fi - if [[ -z "$TYPES" ]] || [[ "$TYPES" == *"smoke"* && "$TYPES" == *"sanity"* && "$TYPES" == *"regression"* ]]; then - if [ "$GENERATE_COVERAGE" == "true" ]; then - hatch run test:${{ matrix.test-config.level }}-cov --cov-report=xml:coverage/coverage.xml --cov-report=html:coverage/htmlcov --junitxml=reports/test-report.xml - else - hatch run test:${{ matrix.test-config.level }} --junitxml=reports/test-report.xml - fi - else - if [ "$GENERATE_COVERAGE" == "true" ]; then - hatch run test:${{ matrix.test-config.level }}-cov -m "$MARKERS" --cov-report=xml:coverage/coverage.xml --cov-report=html:coverage/htmlcov --junitxml=reports/test-report.xml - else - hatch run test:${{ matrix.test-config.level }} -m "$MARKERS" --junitxml=reports/test-report.xml - fi - fi - - name: Upload test results - if: ${{ inputs.publish_results }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: test-results-${{ matrix.test-config.level }}-py${{ matrix.python-version }}-${{ - github.run_id }} - path: reports/ - retention-days: ${{ inputs.retention_days }} - if-no-files-found: warn - - name: Upload coverage report - if: ${{ inputs.publish_results && (inputs.generate_coverage == true || matrix.test-config.coverage == true) }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: coverage-report-${{ matrix.test-config.level }}-py${{ matrix.python-version - }}-${{ github.run_id }} - path: coverage/ - retention-days: ${{ inputs.retention_days }} - if-no-files-found: warn diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml deleted file mode 100644 index f2c0680..0000000 --- a/.github/workflows/development.yml +++ /dev/null @@ -1,96 +0,0 @@ ---- -# ============================================================================= -# development.yml -# -# Triggers: -# pull_request → main -# -# Stage 1 — Quality & Security -# Stage 2 — Tests (Unit & Integration) -# Stage 3 — Docs (Conditional on changes) -# Stage 4 — Build Package -# Stage 5 — Comment on PR -# ============================================================================= -name: "CI — Development" -on: - pull_request: - branches: - - main -concurrency: - group: "dev-${{ github.head_ref }}" - cancel-in-progress: true -permissions: - contents: read -jobs: - - # ── Determine Changes ────────────────────────────────────────────────────── - changes: - name: "Detect Changes" - runs-on: ubuntu-latest - outputs: - docs: ${{ steps.filter.outputs.docs }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Check for docs changes - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3 - id: filter - with: - filters: | - docs: - - 'docs/**' - - 'mkdocs.yml' - - # ── Stage 1: Quality & Security ──────────────────────────────────────────── - quality: - name: "Quality Gate" - uses: ./.github/workflows/_quality.yml - with: - python_versions: '["3.10", "3.14"]' - permissions: - contents: read - security-events: write - security: - name: "Security Audit" - uses: ./.github/workflows/_security.yml - permissions: - contents: read - security-events: write - - # ── Stage 2: Tests ───────────────────────────────────────────────────────── - tests: - name: "Run Tests" - uses: ./.github/workflows/_tests.yml - with: - test_matrix: >- - [ - {"level": "unit", "types": "smoke, sanity"}, - {"level": "integration", "types": "smoke"} - ] - python_versions: '["3.10", "3.14"]' - generate_coverage: true - publish_results: true - retention_days: 7 - - # ── Stage 3: Docs (Conditional) ──────────────────────────────────────────── - docs: - name: "Build & Deploy Docs" - needs: - - changes - - tests - if: needs.changes.outputs.docs == 'true' && github.event.pull_request.head.repo.fork == - false - uses: ./.github/workflows/_docs.yml - permissions: - contents: write - with: - build_type: "dev-branch" - alias: "pr-${{ github.event.pull_request.number }}" - include_coverage: true - - # ── Stage 4: Build ───────────────────────────────────────────────────────── - build: - name: "Build Package" - uses: ./.github/workflows/_build_package.yml - with: - build_type: "dev" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index cab224d..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -# ============================================================================= -# main.yml -# -# Triggers: -# push → main -# -# Stage 1 — Quality Gate & Security Audit -# Stage 2 — Tests (Unit, Integration, E2E) -# Stage 3 — Documentation (dev) -# ============================================================================= -name: "CI — Main" -on: - push: - branches: - - main -concurrency: - group: "main-${{ github.ref_name }}" - cancel-in-progress: true -permissions: - contents: write - security-events: write - actions: read -jobs: - - # ── Stage 1: Quality Gate & Security ─────────────────────────────────────── - quality: - name: "Quality Gate" - uses: ./.github/workflows/_quality.yml - with: - python_versions: '["3.10", "3.14"]' - security: - name: "Security Audit" - uses: ./.github/workflows/_security.yml - - # ── Stage 2: Tests ───────────────────────────────────────────────────────── - tests: - name: "Run Tests" - uses: ./.github/workflows/_tests.yml - with: - test_matrix: >- - [ - {"level": "unit", "types": "smoke, sanity"}, - {"level": "integration", "types": "smoke, sanity"}, - {"level": "e2e", "types": "smoke"} - ] - python_versions: '["3.10", "3.14"]' - generate_coverage: true - publish_results: true - retention_days: 14 - - # ── Stage 3: Documentation ───────────────────────────────────────────────── - docs: - name: "Build & Deploy Docs" - needs: - - quality - - tests - uses: ./.github/workflows/_docs.yml - with: - build_type: "dev" - alias: "latest" - include_coverage: true diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index c8a31b7..0000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,129 +0,0 @@ ---- -# ============================================================================= -# nightly.yml -# -# Trigger: Cron 00:00 UTC daily + manual workflow_dispatch -# Purpose: Extended regression, security SCA, alpha build, docs & publish -# ============================================================================= -name: "CI — Nightly" -on: - schedule: - - cron: "0 0 * * *" # 00:00 UTC daily - workflow_dispatch: -permissions: - contents: write - packages: write - id-token: write - security-events: write - actions: read - attestations: write -jobs: - - # ── Stage 0: Check Changes ───────────────────────────────────────────────── - check-changes: - name: "Check for Source Changes" - runs-on: ubuntu-latest - outputs: - has_changes: ${{ steps.check.outputs.has_changes }} - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: Check for recent commits - id: check - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "has_changes=true" >> "$GITHUB_OUTPUT" - else - COMMITS=$(git log --since="24 hours ago" --oneline) - if [ -z "$COMMITS" ]; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - else - echo "has_changes=true" >> "$GITHUB_OUTPUT" - fi - fi - - # ── Stage 1: Security Audit ──────────────────────────────────────────────── - security: - name: "Security Audit" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' - uses: ./.github/workflows/_security.yml - - # ── Stage 2: Extended Test Suite ─────────────────────────────────────────── - test: - name: "Extended Tests" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' - uses: ./.github/workflows/_tests.yml - with: - test_matrix: >- - [ - {"level": "unit", "types": "smoke, sanity, regression", "coverage": true}, - {"level": "integration", "types": "smoke, sanity"}, - {"level": "e2e", "types": "smoke, sanity"} - ] - python_versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]' - generate_coverage: false - publish_results: true - retention_days: 14 - - # ── Stage 3: Link Check ──────────────────────────────────────────────────── - link-check: - name: "Link Check" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' - uses: ./.github/workflows/_link-check.yml - with: - fail_on_error: false - - # ── Stage 4: Alpha Docs ──────────────────────────────────────────────────── - docs: - name: "Nightly Docs" - needs: - - check-changes - - test - if: needs.check-changes.outputs.has_changes == 'true' - uses: ./.github/workflows/_docs.yml - with: - build_type: "nightly" - alias: "nightly" - include_coverage: true - - # ── Stage 5: Alpha Build ─────────────────────────────────────────────────── - build: - name: "Nightly Build" - needs: - - check-changes - - test - - security - if: needs.check-changes.outputs.has_changes == 'true' - uses: ./.github/workflows/_build_package.yml - with: - build_type: "nightly" - - # ── Stage 6: Publish Artifacts ───────────────────────────────────────────── - publish: - name: "Publish Nightly" - needs: - - check-changes - - build - if: needs.check-changes.outputs.has_changes == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Download build artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 - with: - name: build-artifact-nightly-${{ github.run_id }} - path: dist/ - - name: Generate artifact attestations - uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v1.5.1 - with: - subject-path: dist/* - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14 - with: - packages-dir: dist/ diff --git a/.github/workflows/pipeline-development.yml b/.github/workflows/pipeline-development.yml new file mode 100644 index 0000000..1df4983 --- /dev/null +++ b/.github/workflows/pipeline-development.yml @@ -0,0 +1,257 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "CI — Development" +on: + pull_request: + branches: + - main +concurrency: + group: "dev-${{ github.head_ref }}" + cancel-in-progress: true +permissions: + contents: write + security-events: write + pull-requests: write +jobs: + # ── Detect Changes ───────────────────────────────────────────────────────── + changes: + name: "Detect Changes" + runs-on: ubuntu-latest + outputs: + docs: ${{ steps.filter.outputs.docs }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check for docs changes + uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: .github/paths-filter.yml + + # ── Stage 1: Quality Gate ────────────────────────────────────────────────── + project-quality: + name: "Project Quality" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Quality + uses: ./.github/actions/project/quality + python-quality: + name: "Python Quality (Py ${{ matrix.python-version }})" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Quality + uses: ./.github/actions/python/quality + with: + python-version: ${{ matrix.python-version }} + rust-quality: + name: "Rust Quality" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Quality + uses: ./.github/actions/rust/quality + oci-quality: + name: "OCI Quality" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Quality + uses: ./.github/actions/oci/quality + + # ── Stage 2: Security Audit ──────────────────────────────────────────────── + project-security: + name: "Project Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Security + uses: ./.github/actions/project/security + python-security: + name: "Python Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Security + uses: ./.github/actions/python/security + with: + python-version: "3.10" + rust-security: + name: "Rust Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Security + uses: ./.github/actions/rust/security + + # ── Stage 3: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build (Py ${{ matrix.python-version }})" + needs: + - python-quality + - python-security + - project-quality + - project-security + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Build + uses: ./.github/actions/python/build + with: + python-version: ${{ matrix.python-version }} + - name: Upload Build Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-dev-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + retention-days: 7 + - name: Create Build Comment Metadata + run: | + mkdir -p dist + echo "📦 **Package Build (Python ${{ matrix.python-version }})**: Wheels and source distributions built successfully." > dist/build-comment.txt + echo "The build artifacts can be downloaded from the [Workflow Run Artifacts Page](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." >> dist/build-comment.txt + - name: Upload Build Comment Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: pr-comment-build-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/build-comment.txt + retention-days: 7 + + # ── Stage 4: Tests ───────────────────────────────────────────────────────── + project-tests: + name: "Project Tests (Links)" + needs: + - project-quality + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Tests + uses: ./.github/actions/project/tests + with: + test-level: "e2e" + python-tests: + name: "Python Tests (Py ${{ matrix.python-version }})" + needs: + - python-build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download Build Artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-dev-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + - name: Run Python Tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-level: "all" + test-category: "smoke" + generate-coverage: "true" + - name: Upload Python Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id + }} + path: coverage/python/ + retention-days: 7 + rust-tests: + name: "Rust Tests" + needs: + - rust-quality + - rust-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Tests + uses: ./.github/actions/rust/tests + with: + test-level: "all" + test-category: "smoke" + generate-coverage: "true" + - name: Upload Rust Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-rust-${{ github.run_id }} + path: coverage/rust/ + retention-days: 7 + + # ── Stage 5: Docs (Conditional) ──────────────────────────────────────────── + docs: + name: "Build & Deploy Docs" + needs: + - changes + - python-tests + - rust-tests + - project-tests + if: needs.changes.outputs.docs == 'true' && github.event.pull_request.head.repo.fork == + false + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Docs Builder + uses: ./.github/actions/project/docs + with: + build-type: "dev-branch" + alias: "pr-${{ github.event.pull_request.number }}" + include-coverage: "true" + deploy: "true" + - name: Create Docs Comment Metadata + run: | + mkdir -p site + echo "📖 **Documentation Preview**: Staging documentation has been successfully built and deployed." > site/docs-comment.txt + echo "You can view the preview here: [PR Staging Documentation](https://markurtz.github.io/rustarium/pr-${{ github.event.pull_request.number }}/)" >> site/docs-comment.txt + - name: Upload Docs Comment Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: pr-comment-docs-${{ github.run_id }} + path: site/docs-comment.txt + retention-days: 7 diff --git a/.github/workflows/pipeline-main.yml b/.github/workflows/pipeline-main.yml new file mode 100644 index 0000000..808dc8e --- /dev/null +++ b/.github/workflows/pipeline-main.yml @@ -0,0 +1,223 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "CI — Main" +on: + push: + branches: + - main +concurrency: + group: "main-${{ github.ref_name }}" + cancel-in-progress: true +permissions: + contents: write + security-events: write +jobs: + # ── Stage 1: Quality Gate ────────────────────────────────────────────────── + project-quality: + name: "Project Quality" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Quality + uses: ./.github/actions/project/quality + python-quality: + name: "Python Quality (Py ${{ matrix.python-version }})" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Quality + uses: ./.github/actions/python/quality + with: + python-version: ${{ matrix.python-version }} + rust-quality: + name: "Rust Quality" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Quality + uses: ./.github/actions/rust/quality + oci-quality: + name: "OCI Quality" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Quality + uses: ./.github/actions/oci/quality + + # ── Stage 2: Security Audit ──────────────────────────────────────────────── + project-security: + name: "Project Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Security + uses: ./.github/actions/project/security + python-security: + name: "Python Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Security + uses: ./.github/actions/python/security + with: + python-version: "3.10" + rust-security: + name: "Rust Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Security + uses: ./.github/actions/rust/security + + # ── Stage 3: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build (Py ${{ matrix.python-version }})" + needs: + - python-quality + - python-security + - project-quality + - project-security + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Build + uses: ./.github/actions/python/build + with: + python-version: ${{ matrix.python-version }} + - name: Upload Build Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-main-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + retention-days: 14 + + # ── Stage 4: Tests ───────────────────────────────────────────────────────── + project-tests: + name: "Project Tests (Links)" + needs: + - project-quality + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Tests + uses: ./.github/actions/project/tests + with: + test-level: "e2e" + python-tests: + name: "Python Tests (Py ${{ matrix.python-version }})" + needs: + - python-build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download Build Artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-main-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + - name: Run Python Tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-level: "all" + test-category: "smoke or sanity" # Run smoke and sanity tests on main pushes + generate-coverage: "true" + - name: Upload Python Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id + }} + path: coverage/python/ + retention-days: 14 + rust-tests: + name: "Rust Tests" + needs: + - rust-quality + - rust-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Tests + uses: ./.github/actions/rust/tests + with: + test-level: "all" + test-category: "smoke or sanity" + generate-coverage: "true" + - name: Upload Rust Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-rust-${{ github.run_id }} + path: coverage/rust/ + retention-days: 14 + + # ── Stage 5: Docs ────────────────────────────────────────────────────────── + docs: + name: "Build & Deploy Docs" + needs: + - project-quality + - python-quality + - rust-quality + - oci-quality + - project-security + - python-security + - rust-security + - project-tests + - python-tests + - rust-tests + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Docs Builder + uses: ./.github/actions/project/docs + with: + build-type: "dev" + alias: "latest" + include-coverage: "true" + deploy: "true" diff --git a/.github/workflows/pipeline-nightly.yml b/.github/workflows/pipeline-nightly.yml new file mode 100644 index 0000000..6da4956 --- /dev/null +++ b/.github/workflows/pipeline-nightly.yml @@ -0,0 +1,336 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "CI — Nightly" +on: + schedule: + - cron: "0 0 * * *" # 00:00 UTC daily + workflow_dispatch: +permissions: + contents: write + packages: write + id-token: write + security-events: write + actions: read + attestations: write +jobs: + # ── Stage 0: Check Changes ───────────────────────────────────────────────── + check-changes: + name: "Check for Source Changes" + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check.outputs.has_changes }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Check for recent commits + id: check + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + else + COMMITS=$(git log --since="24 hours ago" --oneline) + if [ -z "$COMMITS" ]; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + echo "has_changes=true" >> "$GITHUB_OUTPUT" + fi + fi + + # ── Stage 1: Security Audit ──────────────────────────────────────────────── + project-security: + name: "Project Security Audit" + needs: check-changes + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Security + uses: ./.github/actions/project/security + with: + force-run: "true" + python-security: + name: "Python Security Audit" + needs: check-changes + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Security + uses: ./.github/actions/python/security + with: + python-version: "3.10" + force-run: "true" + rust-security: + name: "Rust Security Audit" + needs: check-changes + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Security + uses: ./.github/actions/rust/security + with: + force-run: "true" + oci-security: + name: "OCI Security Audit" + needs: check-changes + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Security + uses: ./.github/actions/oci/security + with: + force-run: "true" + + # ── Stage 2: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build (Py ${{ matrix.python-version }})" + needs: + - check-changes + - python-security + - project-security + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Build + uses: ./.github/actions/python/build + with: + python-version: ${{ matrix.python-version }} + force-run: "true" + - name: Upload Build Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-nightly-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + retention-days: 7 + oci-build: + name: "OCI Build" + needs: + - check-changes + - oci-security + - project-security + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Build + uses: ./.github/actions/oci/build + with: + save-image: "true" + force-run: "true" + - name: Upload OCI Image Tarball + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-oci-nightly-${{ github.run_id }} + path: rustarium-latest.tar + retention-days: 7 + + # ── Stage 3: Tests ───────────────────────────────────────────────────────── + project-tests: + name: "Project Tests (Doc Tests)" + needs: + - check-changes + - project-security + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Doc Tests + uses: ./.github/actions/project/tests + with: + test-level: "integration" + force-run: "true" + project-e2e-tests: + name: "Project E2E Tests" + needs: + - check-changes + - project-security + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project E2E Tests + uses: ./.github/actions/project/tests + with: + test-level: "e2e" + force-run: "true" + python-tests: + name: "Python Tests (Py ${{ matrix.python-version }})" + needs: + - python-build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download Build Artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-nightly-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + - name: Run Python Tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-level: "all" + test-category: "regression" # Run deep regression tests nightly + generate-coverage: "true" + force-run: "true" + - name: Upload Python Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id + }} + path: coverage/python/ + retention-days: 14 + rust-tests: + name: "Rust Tests" + needs: + - check-changes + - rust-security + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Tests + uses: ./.github/actions/rust/tests + with: + test-level: "all" + test-category: "regression" + generate-coverage: "true" + force-run: "true" + - name: Upload Rust Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-rust-${{ github.run_id }} + path: coverage/rust/ + retention-days: 14 + oci-tests: + name: "OCI Tests" + needs: + - oci-build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download OCI Image Tarball + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-oci-nightly-${{ github.run_id }} + - name: Run OCI Structure Tests + uses: ./.github/actions/oci/tests + with: + test-level: "e2e" + force-run: "true" + + # ── Stage 4: Docs ────────────────────────────────────────────────────────── + docs: + name: "Build & Deploy Docs" + needs: + - check-changes + - python-tests + - rust-tests + - project-tests + - project-e2e-tests + if: needs.check-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Docs Builder + uses: ./.github/actions/project/docs + with: + build-type: "nightly" + alias: "nightly" + include-coverage: "true" + deploy: "true" + force-run: "true" + + # ── Stage 5: Publish ─────────────────────────────────────────────────────── + publish-python: + name: "Publish Nightly Python Packages" + needs: + - python-tests + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download All Built Wheels + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + pattern: build-artifact-nightly-py* + path: dist/ + merge-multiple: true + - name: Publish Python Package + uses: ./.github/actions/python/publish + with: + artifact-name: "" + create-release: "false" + publish-oci: + name: "Publish Nightly OCI Image" + needs: + - oci-tests + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download OCI Image Tarball + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-oci-nightly-${{ github.run_id }} + - name: Publish OCI Image + uses: ./.github/actions/oci/publish + with: + image-tag: "nightly" + github-token: ${{ secrets.GITHUB_TOKEN }} + load-image: "true" diff --git a/.github/workflows/pipeline-release.yml b/.github/workflows/pipeline-release.yml new file mode 100644 index 0000000..1f10a84 --- /dev/null +++ b/.github/workflows/pipeline-release.yml @@ -0,0 +1,294 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "CI — Release" +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" # Strict semver: v1.2.3 +permissions: + contents: write + packages: write + id-token: write + security-events: write + attestations: write +jobs: + # ── Stage 1: Security Audit ──────────────────────────────────────────────── + project-security: + name: "Project Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Security + uses: ./.github/actions/project/security + with: + force-run: "true" + python-security: + name: "Python Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Security + uses: ./.github/actions/python/security + with: + python-version: "3.10" + force-run: "true" + rust-security: + name: "Rust Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Security + uses: ./.github/actions/rust/security + with: + force-run: "true" + oci-security: + name: "OCI Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Security + uses: ./.github/actions/oci/security + with: + force-run: "true" + + # ── Stage 2: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build (Py ${{ matrix.python-version }})" + needs: + - python-security + - project-security + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Build + uses: ./.github/actions/python/build + with: + python-version: ${{ matrix.python-version }} + force-run: "true" + - name: Upload Build Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-release-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + retention-days: 7 + oci-build: + name: "OCI Build" + needs: + - oci-security + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Build + uses: ./.github/actions/oci/build + with: + save-image: "true" + force-run: "true" + - name: Upload OCI Image Tarball + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-oci-release-${{ github.run_id }} + path: rustarium-latest.tar + retention-days: 7 + + # ── Stage 3: Tests ───────────────────────────────────────────────────────── + project-tests: + name: "Project Tests (Doc Tests)" + needs: + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Tests + uses: ./.github/actions/project/tests + with: + test-level: "integration" + force-run: "true" + project-e2e-tests: + name: "Project E2E Tests" + needs: + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project E2E Tests + uses: ./.github/actions/project/tests + with: + test-level: "e2e" + force-run: "true" + python-tests: + name: "Python Tests (Py ${{ matrix.python-version }})" + needs: + - python-build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download Build Artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-release-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + - name: Run Python Tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-level: "all" + test-category: "regression" # Run deep regression tests for releases + generate-coverage: "true" + force-run: "true" + - name: Upload Python Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id + }} + path: coverage/python/ + retention-days: 90 + rust-tests: + name: "Rust Tests" + needs: + - rust-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Tests + uses: ./.github/actions/rust/tests + with: + test-level: "all" + test-category: "regression" + generate-coverage: "true" + force-run: "true" + - name: Upload Rust Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-rust-${{ github.run_id }} + path: coverage/rust/ + retention-days: 90 + oci-tests: + name: "OCI Tests" + needs: + - oci-build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download OCI Image Tarball + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-oci-release-${{ github.run_id }} + - name: Run OCI Structure Tests + uses: ./.github/actions/oci/tests + with: + test-level: "e2e" + force-run: "true" + + # ── Stage 4: Versioned Docs ──────────────────────────────────────────────── + docs: + name: "Versioned Docs" + needs: + - python-tests + - rust-tests + - project-tests + - project-e2e-tests + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Docs Builder + uses: ./.github/actions/project/docs + with: + build-type: "release" + alias: "latest" + include-coverage: "true" + deploy: "true" + force-run: "true" + + # ── Stage 5: Publish Artifacts ───────────────────────────────────────────── + publish-python: + name: "Publish Python Release" + needs: + - python-tests + - rust-tests + - project-e2e-tests + - oci-tests + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download All Built Wheels + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + pattern: build-artifact-release-py* + path: dist/ + merge-multiple: true + - name: Publish Python Package + uses: ./.github/actions/python/publish + with: + artifact-name: "" # Not needed since we downloaded all wheels directly to dist/ + create-release: "true" + github-token: ${{ secrets.GITHUB_TOKEN }} + publish-oci: + name: "Publish Release OCI Image" + needs: + - oci-tests + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download OCI Image Tarball + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-oci-release-${{ github.run_id }} + - name: Publish OCI Image + uses: ./.github/actions/oci/publish + with: + image-tag: ${{ github.ref_name }} + github-token: ${{ secrets.GITHUB_TOKEN }} + load-image: "true" diff --git a/.github/workflows/pipeline-weekly.yml b/.github/workflows/pipeline-weekly.yml new file mode 100644 index 0000000..c73007b --- /dev/null +++ b/.github/workflows/pipeline-weekly.yml @@ -0,0 +1,217 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "CI — Weekly" +on: + schedule: + - cron: "0 0 * * 0" # 00:00 UTC every Sunday + workflow_dispatch: +permissions: + contents: read + security-events: write +jobs: + # ── Stage 1: Security Audit ──────────────────────────────────────────────── + project-security: + name: "Project Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Security + uses: ./.github/actions/project/security + with: + force-run: "true" + python-security: + name: "Python Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Security + uses: ./.github/actions/python/security + with: + python-version: "3.10" + force-run: "true" + rust-security: + name: "Rust Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Security + uses: ./.github/actions/rust/security + with: + force-run: "true" + oci-security: + name: "OCI Security Audit" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Security + uses: ./.github/actions/oci/security + with: + force-run: "true" + + # ── Stage 2: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build (Py ${{ matrix.python-version }})" + needs: + - python-security + - project-security + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Python Build + uses: ./.github/actions/python/build + with: + python-version: ${{ matrix.python-version }} + force-run: "true" + - name: Upload Build Artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-weekly-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + retention-days: 7 + oci-build: + name: "OCI Build" + needs: + - oci-security + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run OCI Build + uses: ./.github/actions/oci/build + with: + save-image: "true" + force-run: "true" + - name: Upload OCI Image Tarball + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: build-artifact-oci-weekly-${{ github.run_id }} + path: rustarium-latest.tar + retention-days: 7 + + # ── Stage 3: Tests ───────────────────────────────────────────────────────── + project-tests: + name: "Project Tests (Doc Tests)" + needs: + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project Tests + uses: ./.github/actions/project/tests + with: + test-level: "integration" + force-run: "true" + project-e2e-tests: + name: "Project E2E Tests" + needs: + - project-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Project E2E Tests + uses: ./.github/actions/project/tests + with: + test-level: "e2e" + force-run: "true" + python-tests: + name: "Python Tests (Py ${{ matrix.python-version }})" + needs: + - python-build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download Build Artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-weekly-py${{ matrix.python-version }}-${{ github.run_id }} + path: dist/ + - name: Run Python Tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-level: "all" + test-category: "regression" + generate-coverage: "true" + force-run: "true" + - name: Upload Python Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id + }} + path: coverage/python/ + retention-days: 14 + rust-tests: + name: "Rust Tests" + needs: + - rust-security + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Rust Tests + uses: ./.github/actions/rust/tests + with: + test-level: "all" + test-category: "regression" + generate-coverage: "true" + force-run: "true" + - name: Upload Rust Coverage Report + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverage-report-unit-rust-${{ github.run_id }} + path: coverage/rust/ + retention-days: 14 + oci-tests: + name: "OCI Tests" + needs: + - oci-build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download OCI Image Tarball + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + name: build-artifact-oci-weekly-${{ github.run_id }} + - name: Run OCI Structure Tests + uses: ./.github/actions/oci/tests + with: + test-level: "e2e" + force-run: "true" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 141c927..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,96 +0,0 @@ ---- -# ============================================================================= -# release.yml -# -# Trigger: Push of a version tag matching v*.*.* -# Purpose: Final verification, immutable build, versioned docs, release publish -# ============================================================================= -name: "CI — Release" -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" # Strict semver: v1.2.3 -permissions: - contents: write - packages: write - id-token: write - security-events: write - attestations: write -jobs: - - # ── Stage 1: Security Audit ──────────────────────────────────────────────── - security: - name: "Security Audit" - uses: ./.github/workflows/_security.yml - - # ── Stage 2: Link Check ──────────────────────────────────────────────────── - link-check: - name: "Link Check" - uses: ./.github/workflows/_link-check.yml - with: - fail_on_error: true - - # ── Stage 3: Full Verification ───────────────────────────────────────────── - test: - name: "Full Verification Suite" - uses: ./.github/workflows/_tests.yml - with: - test_matrix: >- - [ - {"level": "unit", "types": "smoke, sanity, regression"}, - {"level": "integration", "types": "smoke, sanity, regression"}, - {"level": "e2e", "types": "smoke, sanity, regression"} - ] - python_versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]' - generate_coverage: true - publish_results: true - retention_days: 90 - - # ── Stage 4: Versioned Docs ──────────────────────────────────────────────── - docs: - name: "Versioned Docs" - needs: test - uses: ./.github/workflows/_docs.yml - with: - build_type: "release" - alias: "latest" - include_coverage: true - - # ── Stage 5: Immutable Build ─────────────────────────────────────────────── - build: - name: "Release Build" - needs: - - test - - security - uses: ./.github/workflows/_build_package.yml - with: - build_type: "release" - - # ── Stage 6: Publish Artifacts ───────────────────────────────────────────── - publish: - name: "Publish Release" - needs: build - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Download build artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 - with: - name: build-artifact-release-${{ github.run_id }} - path: dist/ - - name: Generate artifact attestations - uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v1.5.1 - with: - subject-path: dist/* - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14 - with: - packages-dir: dist/ - - name: Create GitHub Release - if: ${{ github.ref_type == 'tag' }} - uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 - with: - files: dist/* - generate_release_notes: true - prerelease: false diff --git a/.github/workflows/development_cleanup.yml b/.github/workflows/util-development-cleanup.yml similarity index 68% rename from .github/workflows/development_cleanup.yml rename to .github/workflows/util-development-cleanup.yml index f28a465..d30f427 100644 --- a/.github/workflows/development_cleanup.yml +++ b/.github/workflows/util-development-cleanup.yml @@ -1,6 +1,6 @@ --- # ============================================================================= -# development_cleanup.yml +# util-development-cleanup.yml # # Triggers: # pull_request → closed (merged or abandoned) @@ -22,22 +22,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Configure Git Credentials run: | git config user.name github-actions[bot] git config user.email github-actions[bot]@users.noreply.github.com - - name: Install Mike - run: pip install mike - name: Delete PR docs preview run: |- PR_PATH="pr-${{ github.event.pull_request.number }}" echo "Deleting docs preview at path: ${PR_PATH}" - # Delete the versioned path from gh-pages; ignore errors if it doesn't exist - mike delete --push "${PR_PATH}" || echo "No docs preview found for ${PR_PATH}, nothing to clean up." + git fetch origin gh-pages:gh-pages || true + git checkout gh-pages || exit 0 + if [ -d "$PR_PATH" ]; then + git rm -rf "$PR_PATH" + git commit -m "docs: cleanup ephemeral docs preview for $PR_PATH" || echo "No changes to commit" + git push origin gh-pages + else + echo "No docs preview found for $PR_PATH, nothing to clean up." + fi diff --git a/.github/workflows/util-pr-comment.yml b/.github/workflows/util-pr-comment.yml new file mode 100644 index 0000000..2c58a0f --- /dev/null +++ b/.github/workflows/util-pr-comment.yml @@ -0,0 +1,162 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================= +# util-pr-comment.yml +# +# Triggers: +# workflow_run → CI — Development (completed) +# +# Resolves the GitHub Actions fork permission issue. Workflows triggered +# via `pull_request` from a fork do not have `write` permissions to post +# comments. `workflow_run` executes in the context of the base repository +# and can safely download artifacts, compile reports, and comment on the PR. +# ============================================================================= +name: "PR Commenter" +on: + workflow_run: + workflows: + - "CI — Development" + types: + - completed +permissions: + pull-requests: write + actions: read +jobs: + post_comment: + name: "Post PR Status Comment" + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' + steps: + - name: Download all artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + with: + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Compile and Post PR Status Comments" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: |- + const fs = require('fs'); + const path = require('path'); + const workflowRun = context.payload.workflow_run; + // 1. Get the pull request associated with the commit + const response = await github.rest.search.issuesAndPullRequests({ + q: `is:pr repo:${context.repo.owner}/${context.repo.repo} sha:${workflowRun.head_sha}` + }); + if (response.data.total_count === 0) { + console.log('No PR found for this commit.'); + return; + } + const prNumber = response.data.items[0].number; + // 2. Scan workspace for downloaded artifacts + const workspace = process.env.GITHUB_WORKSPACE || '.'; + const dirs = fs.readdirSync(workspace); + let docsComment = ""; + const buildComments = []; + let pythonCoverageReports = ""; + let rustCoverageReports = ""; + let hasCoverage = false; + for (const dirName of dirs) { + const dirPath = path.join(workspace, dirName); + if (!fs.statSync(dirPath).isDirectory()) continue; + // Parse docs comment + if (dirName.startsWith('pr-comment-docs-')) { + const filePath = path.join(dirPath, 'docs-comment.txt'); + if (fs.existsSync(filePath)) { + docsComment = fs.readFileSync(filePath, 'utf8'); + } + } + // Parse build comment + if (dirName.startsWith('pr-comment-build-py')) { + const filePath = path.join(dirPath, 'build-comment.txt'); + if (fs.existsSync(filePath)) { + buildComments.push(fs.readFileSync(filePath, 'utf8')); + } + } + // Parse python coverage + if (dirName.startsWith('coverage-report-unit-python-py')) { + const pyVerMatch = dirName.match(/python-(py\d+\.\d+)/); + const pyVer = pyVerMatch ? pyVerMatch[1] : "Python"; + const funcCovPath = path.join(dirPath, 'coverage_tests-func.md'); + if (fs.existsSync(funcCovPath)) { + const content = fs.readFileSync(funcCovPath, 'utf8'); + pythonCoverageReports += `#### 🐍 Python Coverage (${pyVer} — Functional Tests)\n\n${content}\n\n`; + hasCoverage = true; + } + const e2eCovPath = path.join(dirPath, 'coverage_tests-e2e.md'); + if (fs.existsSync(e2eCovPath)) { + const content = fs.readFileSync(e2eCovPath, 'utf8'); + pythonCoverageReports += `#### 🐍 Python Coverage (${pyVer} — E2E Tests)\n\n${content}\n\n`; + hasCoverage = true; + } + } + // Parse rust coverage + if (dirName.startsWith('coverage-report-unit-rust-')) { + const funcCovPath = path.join(dirPath, 'coverage_tests-func.md'); + if (fs.existsSync(funcCovPath)) { + const content = fs.readFileSync(funcCovPath, 'utf8'); + rustCoverageReports += `#### 🦀 Rust Coverage (Functional Tests)\n\n${content}\n\n`; + hasCoverage = true; + } + } + } + // 3. Compile and Post Comments + // Separate Comment: Docs Preview (if built) + if (docsComment) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: docsComment + }); + console.log("Posted Documentation Preview comment."); + } + // Separate Comments: Build Artifacts (if created) + for (const buildComment of buildComments) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: buildComment + }); + console.log("Posted Package Build comment."); + } + // Separate Comment: Status and Test Coverage compilation + const status = workflowRun.conclusion; + let statusBody = `## CI Development Pipeline Status\n\n`; + if (status === 'success') { + statusBody += `✅ **Pipeline**: Completed successfully. [View Run Details](${workflowRun.html_url})\n\n`; + } else { + statusBody += `❌ **Pipeline**: Failed or was cancelled. [Check logs](${workflowRun.html_url})\n\n`; + } + if (hasCoverage) { + statusBody += `### 📊 Compiled Code Coverage Results\n\n`; + if (pythonCoverageReports) { + statusBody += `### Python Test Suites\n\n${pythonCoverageReports}`; + } + if (rustCoverageReports) { + statusBody += `### Rust Test Suites\n\n${rustCoverageReports}`; + } + } else { + statusBody += `No code coverage reports were compiled for this run.`; + } + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: statusBody + }); + console.log("Posted Pipeline Status and Coverage comment."); diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml deleted file mode 100644 index ae770df..0000000 --- a/.github/workflows/weekly.yml +++ /dev/null @@ -1,37 +0,0 @@ ---- -# ============================================================================= -# weekly.yml -# -# Trigger: Cron 00:00 UTC every Sunday + manual workflow_dispatch -# Purpose: Dependency hygiene, full test suite regression. -# ============================================================================= -name: "CI — Weekly" -on: - schedule: - - cron: "0 0 * * 0" # 00:00 UTC every Sunday - workflow_dispatch: # Allow manual trigger -permissions: - contents: read - security-events: write -jobs: - - # ── Stage 1: Security Audit ──────────────────────────────────────────────── - security: - name: "Security Audit" - uses: ./.github/workflows/_security.yml - - # ── Stage 2: Full Test Suite ─────────────────────────────────────────────── - test: - name: "Full Verification Suite" - uses: ./.github/workflows/_tests.yml - with: - test_matrix: >- - [ - {"level": "unit", "types": "smoke, sanity, regression"}, - {"level": "integration", "types": "smoke, sanity, regression", "coverage": true}, - {"level": "e2e", "types": "smoke, sanity, regression", "coverage": true} - ] - python_versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]' - generate_coverage: false - publish_results: true - retention_days: 14 diff --git a/.gitignore b/.gitignore index 63123cb..25422d6 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ cmake-build-*/ .run/ # VS Code / Cursor +.vscode .vscode/* .vscode/settings.json .vscode/tasks.json @@ -80,7 +81,7 @@ bin/ .zed/ # Xcode -build/ +/build/ *.pbxuser !default.pbxuser *.mode1v3 @@ -167,7 +168,7 @@ __pycache__/ *$py.class *.so .Python -build/ +/build/ develop-eggs/ dist/ downloads/ @@ -189,9 +190,6 @@ MANIFEST .hatch/ .hypothesis/ .pytest_cache/ -.mypy_cache/ -.dmypy.json -dmypy.json .pyre/ .pytype/ .ruff_cache/ @@ -287,6 +285,7 @@ bld/ # Rust # target/ (Moved to avoid conflict with Java target/ if in same dir, but fine to ignore twice) **/*.rs.bk +.bin/ # Cargo.lock # Note: Cargo.lock should typically be committed for executables # Go @@ -391,3 +390,14 @@ cdk.out/ *.csv *.dat *.seed + +# Trivy cache +.trivycache/ + +# Generated phmdoctest files +/.tests/ + +# Generated reference documentation +/.docs/ + + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 2f8b420..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,37 +0,0 @@ ---- -default_install_hook_types: - - pre-commit -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - args: - - --unsafe - - id: check-merge-conflict - - repo: local - hooks: - - id: lint-check - name: Quality and Formatting (Hatch) - entry: hatch run lint:check - language: python - additional_dependencies: - - hatch - - uv - types_or: - - python - - markdown - - yaml - pass_filenames: false - - id: type-check - name: Type Check (Hatch) - entry: hatch run types:check - language: python - additional_dependencies: - - hatch - - uv - types: - - python - pass_filenames: false diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..59efb2b --- /dev/null +++ b/.yamllint @@ -0,0 +1,29 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +extends: default + +ignore: | + .venv/ + venv/ + target/ + build/ + dist/ + +rules: + line-length: + max: 300 + truthy: + check-keys: false + document-start: disable diff --git a/AGENTS.md b/AGENTS.md index 4b7f508..8e01f40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,6 @@ ## Critical Constraints > [!CAUTION] -> > 1. **Never commit secrets.** Do not add API keys, tokens, or credentials anywhere. > 1. **Do not modify `LICENSE` or `NOTICE`.** These are legally binding. > 1. **Do not modify workflow trigger conditions** without human review. @@ -22,17 +21,19 @@ ## Repository Layout - `.github/workflows/`: CI/CD pipelines. Files prefixed with `_` are reusable templates. -- `docs/`: MkDocs Material source. +- `docs/`: Zensical documentation source. - `src/`: Primary application source code. -- `tests/`: Organized into `unit/`, `integration/`, and `e2e/`. +- `tests/`: Organized into `python/unit/`, `python/integration/`, and `e2e/`. ## Executable Commands -- **Linting:** `hatch run lint:check` (Ruff & mdformat) and `hatch run types:check` (Mypy) -- **Pre-commit:** `pre-commit run --all-files` (Runs formatting and quality checks) -- **Testing:** `hatch run test:all` (Pytest) and `hatch run test:all-cov` (Coverage) -- **Docs:** `hatch run docs:serve` / `hatch run docs:build` -- **Build:** `hatch build` +- **Formatting:** `hatch run all:format` (or `hatch run python:format`, `hatch run rust:format`, `hatch run project:format`, `hatch run oci:format`) +- **Linting:** `hatch run all:lint` (cascades to all environments) or individual checks (e.g., `hatch run python:lint`, `hatch run project:lint`) +- **Type Checking:** `hatch run all:types` (cascades to python/rust check targets) or environment-specific +- **Testing:** `hatch run all:tests` (runs python + rust unit/integration and project e2e), `hatch run python:tests` (with optional `-cov` suffix) +- **Security Audits:** `hatch run all:security` or environment-specific (e.g., `hatch run python:security`) +- **Docs:** `hatch run project:docs-serve` (local dev server) / `hatch run project:docs` (static build using Zensical) +- **Build:** `hatch build` (Maturin Python package build) or `hatch run all:build` (builds python, OCI, docs) ## Code Style & Patterns @@ -52,8 +53,7 @@ ## Documentation -- **`docs/`** and **`mkdocs.yml`** control the site. Do not create docs outside the `nav:` tree. -- `docs/index.md` dynamically includes `README.md` via MkDocs snippets. +- **`docs/`** and **`zensical.toml`** control the site. - Use `{{placeholder}}` variables for templated fields (e.g., `rustarium`, `markurtz`). ## Agent Notes diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..069d72e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,415 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustarium-core" +version = "0.0.1" +dependencies = [ + "bincode", + "pyo3", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9bbce53 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,46 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.package] +version = "0.0.1" +edition = "2021" +authors = ["Mark Kurtz"] +license = "Apache-2.0" +repository = "https://github.com/markurtz/rustarium" + +[workspace.dependencies] +pyo3 = { version = "0.24.1", features = ["abi3-py310"] } +tokio = { version = "1.38", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +bincode = "1.3" + +[workspace.metadata.bin] +cargo-audit = { version = "0.22.1", locked = true } +cargo-deny = { version = "0.19.6", locked = true } + +[profile.dev] +split-debuginfo = "unpacked" + +[profile.dev.package."*"] +opt-level = 3 + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 diff --git a/DEVELOPING.md b/DEVELOPING.md index 422bf78..56ca1ad 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -2,203 +2,505 @@ This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards. -## Prerequisites +## Setup & Prerequisites -Ensure your system meets the following requirements before getting started: +Ensure your system meets the requirements below to establish a consistent local development environment, or utilize our containerized development setup. -- **[Docker](https://docs.docker.com/get-docker/)** (Recommended for isolated environments) -- **[Git](https://git-scm.com/)** (Version control) -- **[Python](https://www.python.org/)** 3.10+ -- **[Hatch](https://hatch.pypa.io/)** (Project manager) +### Supported Operating Systems + +- **macOS & Linux**: Standard operating systems that are fully supported, actively tested, and maintained. +- **Windows**: Not officially tested or maintained. Windows users encountering issues should use the [Development Environment Container](#development-environment-container-devcontainer) setup. + +### Development Environment Container (.devcontainer) + +- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **Dev Containers** extension installed. +- **Usage**: + 1. Clone this repository: `git clone https://github.com/markurtz/rustarium.git` + 1. Open the project folder in VS Code. + 1. A prompt will appear: "Reopen in Container". Click it to launch the environment. + 1. VS Code will build the container, install the stable Rust toolchain, and automatically run `uv sync --all-groups --all-extras` to install and sync the Python environment. > [!NOTE] -> We strongly recommend using our Docker setup to ensure your local environment exactly matches our CI/CD pipelines. +> **Local `.venv` vs. Hatch Environments**: +> The `uv sync` command creates a local `.venv` in the project root solely to provide VS Code extensions (like Pylance and Ruff) with a standard environment for editor autocomplete, hover information, and in-editor diagnostics. All command-line and automated task execution (formatting, linting, testing, building) is managed via **Hatch** isolated environments (`hatch run ...`). Do not activate or modify this root `.venv` directly for running tasks. + +### Local Setup + +- **[Git](https://git-scm.com/)**: Version control tool. Refer to the [Git Documentation](https://git-scm.com/doc) for installation instructions. +- **[Docker](https://www.docker.com/)**: Container management system. Install via the [Docker Installation Guide](https://docs.docker.com/get-docker/). +- **System-level Build Tools**: Compiling native Rust extensions for Python (via `Maturin`) requires a C compiler and development headers: + - **macOS**: Install Xcode Command Line Tools by running `xcode-select --install`. + - **Linux (Debian/Ubuntu)**: Install `build-essential`, `clang`, `pkg-config`, and `libssl-dev`. + - **Linux (Fedora/RHEL)**: Install `development-tools`, `clang`, `pkg-config`, and `openssl-devel`. +- **[Python](https://www.python.org/) 3.10 - 3.14**: Core runtime environment. Install via the [Python Downloads Page](https://www.python.org/downloads/). +- **[Rust & rustup](https://rustup.rs/)**: Stable compiler toolchain. Install via the [Rustup Installer](https://rustup.rs/). +- **[uv](https://docs.astral.sh/uv/)**: Fast package installer and resolver. Install via the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/). +- **[Hatch](https://hatch.pypa.io/)**: Project workflow orchestrator. Install via the [Hatch installation guide](https://hatch.pypa.io/latest/install/). If you have `uv` installed, we recommend installing Hatch cleanly as a tool using: + ```bash + uv tool install hatch + ``` + to avoid polluting your global system packages. +- **Rust Dev Tools**: Local security audit tools (`cargo-audit` and `cargo-deny`) are managed via `cargo-run-bin`. The environment bootstraps `cargo-run-bin` automatically on environment creation/update via Hatch's `post-install-commands`. When you run a task in the `rust` environment (such as `hatch run rust:security`), the correct tool versions (locked in the root workspace `Cargo.toml`) will be automatically compiled and cached in the local `.bin/` folder. You can also explicitly trigger the bootstrap command using: + ```bash + hatch run rust:install-tools + ``` -## Quick Start (Docker) +> [!TIP] +> **Editor Autocomplete Setup (Local)**: +> For local development outside of the Dev Container, if you want your editor (VS Code, PyCharm, etc.) to resolve imports and provide autocomplete/diagnostics, run `uv sync --all-groups --all-extras` once to create the local `.venv`. -> [!IMPORTANT] -> The `Dockerfile` and `docker-compose.yml` files included in this template are **placeholders** that must be filled in for your specific language stack before they are usable. The default `CMD` in both files will exit with an error if run without modification. -> -> Once implemented, you can spin up the development environment with: +## Developer Quickstart + +Once your environment is set up (either via the Dev Container or manually), follow this consolidated workflow for a standard development cycle: + +- **Branch & Code**: Create your feature branch and make changes: + ```bash + git checkout -b feat/my-contribution + ``` +- **Quality Assurance (Unified)**: Automatically format code, lint, type check, and run security scans across all environments: + ```bash + hatch run all:quality + ``` + *(Alternatively, you can run individual checks if preferred: `hatch run all:format`, `hatch run all:lint`, `hatch run all:types`, or `hatch run all:security`)* +- **Test (Unified)**: Run all unit, integration, and E2E tests with coverage: + ```bash + hatch run all:tests-cov + ``` + *(For running tests without coverage: `hatch run all:tests`)* +- **Build (Unified)**: Compile package artifacts (source & wheels) and build the OCI container image *(requires Docker daemon to be running for the OCI phase)*: + ```bash + hatch run all:build + ``` + *(To build only the Python wheel locally: `hatch build`)* +- **Serve Documentation**: Serve documentation locally (this automatically builds the site): + ```bash + hatch run all:docs-serve + ``` +- **Push**: Push your changes to open a Pull Request: + ```bash + git push -u origin feat/my-contribution + ``` + +## Hatch Development Environments Overview + +Our build, verification, and execution pipelines are partitioned into target-specific environments using Hatch. This ensures isolation, prevents dependency bloat, and standardizes workflows: + +- **`default`**: The base environment template. It configures shared environment variables (such as target paths, directory structures, and script file paths) and installs the core dependency groups. +- **`all`**: The orchestrator environment. It defines cascading workflows to run formatting, linting, typing, security scanning, testing, and documentation generation across all components sequentially or concurrently. +- **`python`**: Encompasses Python-specific verification tools including Ruff for linting/formatting, Ty for type-checking, Pytest for testing, and Typer for CLI documentation generation. +- **`rust`**: Handles compiling, testing (`cargo test`), linting (`clippy`), type checking (`cargo check`), and API documentation generation (`cargo doc`) for the underlying Rust extension modules. +- **`oci`**: Manages OCI container builds (`docker build`), compose verification (`docker compose config`), linting (`hadolint`), security auditing (`trivy`, `dockle`), and container structure tests (`cstest`). +- **`project`**: Targets repository-wide configuration and file standards, including Markdown formatting (`mdformat`), configuration checkouts (`yamlfix`, `yamllint`, `taplo`), security baselines (`detect-secrets`, `checkov`), link checkers, and site compilation (using the **Zensical** static site generator/documentation compiler). + +## Coding Workflows + +All development commands are unified under [pyproject.toml](./pyproject.toml) and managed using Hatch. The commands are generally invoked using the format: + +```bash +hatch run [ENVIRONMENT]:[SCRIPT] +``` + +For orchestrating tasks across all environments, use the `all` environment scripts: + +```bash +hatch run all:[SCRIPT] +``` + +### Quality Assurance & Static Analysis + +This workflow enforces code quality, style conventions, static type correctness, and security policies across all codebase layers. + +> [!TIP] +> **Unified Quality Check**: +> You can run all formatting, linting, type-checking, and security scans across all environments in a single command using the unified quality check: > > ```bash -> git clone https://github.com/markurtz/rustarium.git -> cd rustarium -> -> # Build and start the development environment in the background -> docker-compose up -d --build +> hatch run all:quality > ``` -To view the logs of your running containers: +| Environment | Formatting Command | Linting Command | Type-Checking Command | Security Auditing Command | +| :--------------------- | :------------------------- | :----------------------- | :--------------------------- | :--------------------------- | +| **All / Orchestrator** | `hatch run all:format` | `hatch run all:lint` | `hatch run all:types` | `hatch run all:security` | +| **Python** | `hatch run python:format` | `hatch run python:lint` | `hatch run python:types` | `hatch run python:security` | +| **Rust** | `hatch run rust:format` | `hatch run rust:lint` | `hatch run rust:types` | `hatch run rust:security` | +| **OCI** | `hatch run oci:format` | `hatch run oci:lint` | `hatch run oci:types` \* | `hatch run oci:security` | +| **Project** | `hatch run project:format` | `hatch run project:lint` | `hatch run project:types` \* | `hatch run project:security` | + +*\* Note: Type checking is not applicable for OCI and Project environments; executing these commands will output an information message.* -```bash -docker-compose logs -f -``` +#### Code Formatting -## Local Setup +- **Tools / Methodology / Rationale**: + - **Python**: Uses `ruff` to automatically check/fix imports and format code layout. This delivers high-performance style standardization. + - **Rust**: Uses `cargo fmt` to enforce the official Rust layout style and `cargo clippy --fix` to safely resolve compiler styling suggestions. + - **OCI**: Uses `dclint` (via a helper script) to auto-format Docker Compose files. While `dclint` is primarily a compose linter, the format step (`hatch run oci:format`) executes it with the `--fix` flag to automatically correct lint errors and standard style issues in place. (Dockerfile linting/validation is handled separately by `hadolint`). + - **Project**: Employs `mdformat` for Markdown, `yamlfix` for YAML files, and `taplo` for TOML file formatting to maintain a uniform structure for all configuration and documentation files. +- **Expected Outputs & Locations**: + - In-place modifications applied directly to the files targeted by the respective environment variables: `PYTHON_TARGETS`, `RUST_MANIFEST`, `MDFORMAT_TARGETS` (Markdown targets), `YAML_TARGETS`, and `TOML_TARGETS`. + +#### Linting & Verification -If you prefer to develop directly on your host machine, this project uses [uv](https://docs.astral.sh/uv/) for environment management and dependency resolution, alongside [Hatch](https://hatch.pypa.io/) as our command orchestrator. +- **Tools / Methodology / Rationale**: + - **Python**: Runs `ruff check` and `ruff format --check` to verify compliance with PEP 8 and project style guidelines without modifying files. + - **Rust**: Executes `cargo clippy` and `cargo fmt --check` to run comprehensive static analysis lints, treating all warnings as errors (`-D warnings`). + - **OCI**: Uses `hadolint` to validate Dockerfile syntax and standard practices, and runs `docker compose config` to verify the syntactic and semantic validity of compose files. + - **Project**: Runs `mdformat --check` to check Markdown formatting, `yamlfix --check` and `yamllint` for YAML files, and `taplo check` for TOML configuration syntax. +- **Expected Outputs & Locations**: + - Summary reports, warnings, and errors output directly to the terminal stdout/stderr. Standard exit codes (non-zero on failures) are used to gate CI pipelines. -> [!NOTE] -> **A note on shared tooling:** This project uses [MkDocs](https://www.mkdocs.org/) for documentation. +#### Static Type Checking + +- **Tools / Methodology / Rationale**: + - **Python**: Employs Astral's `ty check` frontend to statically analyze and verify Python type annotations. + - **Rust**: Executes `cargo check --all-targets` to quickly analyze the Rust codebase and verify compile-time type safety without generating binary artifacts. +- **Expected Outputs & Locations**: + - Type checker error listings and tracebacks are printed to the terminal console. + +#### Security & Vulnerability Auditing + +- **Tools / Methodology / Rationale**: + - **Python**: Employs `semgrep` for semantic pattern matching, `pip-audit` to detect known vulnerabilities in Python packages, and `ruff check --select S` to check for security vulnerabilities. + - **Rust**: Uses `cargo-run-bin` (`cargo bin cargo-audit` and `cargo bin cargo-deny`) to scan dependencies for CVEs reported in the Rust Sec Advisory Database, and to audit licenses and sources. The required versions of these tools are locked in the root workspace `Cargo.toml` and cached locally in `.bin/`. + - **OCI**: Scans built containers using `dockle` (verifies image best practices/secrets) and `trivy` (scans OS-level packages for CVEs). + - **Project**: Employs `detect-secrets` to scan for accidentally committed secrets against a baseline, and `checkov` to scan infrastructure-as-code files and development configurations. +- **Expected Outputs & Locations**: + - Standard reports output to the console. + - Project environment updates and validates the secrets baseline file located at `.detect-secrets.scan.json`. Run `hatch run project:security-update` to update this baseline file. + +### Testing Strategy & Suites + +Our testing strategy is split into component-level, integration-level, and system-level suites, each of which supports code coverage reporting. + +#### Coverage Configurations & Directories + +Code coverage runs collect data during test executions and format them into human-readable Markdown summaries. + +- **Python Coverage**: Configured to output to `coverage/python/` (`PYTHON_COV_DIR`). The test suites automatically output terminal reports and compile Markdown reports (e.g., `coverage_tests-unit.md`). +- **Rust Coverage**: Configured to output to `coverage/rust/` (`RUST_COV_DIR`). It utilizes `cargo llvm-cov` to collect coverage, outputs `.json` metrics, and validates them against coverage gates via `covgate`, exporting Markdown reports (e.g., `coverage_tests-unit.md`). + +| Test Suite | Python Command | Rust Command | OCI Command | Project Command | All Command | +| :------------------------ | :-------------------------------- | :------------------------------ | :---------------------------- | :-------------------------------- | :----------------------------- | +| **All Local Tests** | N/A | N/A | N/A | N/A | `hatch run all:tests` | +| **All Tests + Coverage** | N/A | N/A | N/A | N/A | `hatch run all:tests-cov` | +| **Functional Tests** | `hatch run python:tests-func` | `hatch run rust:tests-func` | N/A | N/A | `hatch run all:tests-func` | +| **Func Tests + Coverage** | `hatch run python:tests-func-cov` | `hatch run rust:tests-func-cov` | N/A | N/A | `hatch run all:tests-func-cov` | +| **Unit Tests** | `hatch run python:tests-unit` | `hatch run rust:tests-unit` | N/A | N/A | `hatch run all:tests-unit` | +| **Unit Tests + Coverage** | `hatch run python:tests-unit-cov` | `hatch run rust:tests-unit-cov` | N/A | N/A | `hatch run all:tests-unit-cov` | +| **Integration Tests** | `hatch run python:tests-int` | `hatch run rust:tests-int` | N/A | `hatch run project:tests-int` | `hatch run all:tests-int` | +| **Int Tests + Coverage** | `hatch run python:tests-int-cov` | `hatch run rust:tests-int-cov` | N/A | `hatch run project:tests-int-cov` | `hatch run all:tests-int-cov` | +| **End-to-End Tests** | `hatch run python:tests-e2e` | N/A | `hatch run oci:tests-e2e` | `hatch run project:tests-e2e` | `hatch run all:tests-e2e` | +| **E2E Tests + Coverage** | `hatch run python:tests-e2e-cov` | N/A | `hatch run oci:tests-e2e-cov` | `hatch run project:tests-e2e-cov` | `hatch run all:tests-e2e-cov` | + +*\* Note: While Hatch commands for `N/A` cells can technically be run (and will print a message stating that the test suite is not defined for that environment), they have no logical test targets or execution paths. They are marked `N/A` for clarity.*| + +#### Test Suites Breakdown + +#### Full Suite (`tests` / `tests-cov`) + +- **Methodology & Rationale**: Executes all local functional and E2E tests across all environments to ensure complete validation of the codebase before code integration. +- **Expected Outputs & Locations**: Unified console log output, combined test summaries, and all coverage Markdown files compiled under `coverage/python/` and `coverage/rust/`. + +#### Functional Testing (`tests-func` / `tests-func-cov`) + +- **Methodology & Rationale**: Executes both unit and integration tests under the targeted environment to verify logical flows and subsystem communication. +- **Expected Outputs & Locations**: + - **Python**: Outputs to console and `coverage/python/coverage_tests-func.md`. + - **Rust**: Compiles coverage details into `coverage/rust/coverage_tests-func.json` and checks the gates to output `coverage/rust/coverage_tests-func.md`. + +#### Unit Testing (`tests-unit` / `tests-unit-cov`) + +- **Methodology & Rationale**: + - **Python**: Runs isolated tests under `tests/python/unit` via `pytest`. Focuses on validating individual modules and class behaviors. + - **Rust**: Runs isolated tests using `cargo test --lib --bins`. Validates pure internal Rust crate logic. +- **Expected Outputs & Locations**: + - **Python**: Outputs `coverage/python/coverage_tests-unit.md`. + - **Rust**: Outputs `coverage/rust/coverage_tests-unit.md` and `coverage/rust/coverage_tests-unit.json`. + +#### Integration Testing (`tests-int` / `tests-int-cov`) + +- **Methodology & Rationale**: + - **Python**: Runs tests under `tests/python/integration` via `pytest` to verify interactions between Python modules. + - **Rust**: Runs integration test targets defined under the `tests/` directory of the Rust crate via `cargo test --test '*'` to verify cross-module/macro integration. + - **Project**: Runs documentation code block tests. It utilizes `scripts/generate_doc_tests.py` to parse Markdown files and compile code block assertions under `.tests/docs` (`DOC_TESTS_PATH`), which are then executed using `pytest`. +- **Expected Outputs & Locations**: + - **Python**: Outputs `coverage/python/coverage_tests-int.md`. + - **Rust**: Outputs `coverage/rust/coverage_tests-int.md` and `coverage/rust/coverage_tests-int.json`. + - **Project**: Verifies doc tests compile and pass; outputs progress to stdout. + +#### End-to-End Testing (`tests-e2e` / `tests-e2e-cov`) + +- **Methodology & Rationale**: + - **Python**: Compiles Python packages with `hatch build`, force reinstalls them via `pip`, and runs pytest against `tests/e2e` (`E2E_TESTS`) to verify CLI commands and package distribution paths in a black-box environment. + - **OCI**: Builds the OCI image and executes Google's Container Structure Tests (`cstest` via `scripts/run_oci.py`) to confirm that the image metadata, file layouts, and execution endpoints conform to specifications. + - **Project**: Executes `scripts/check_links.py` to recursively crawl project documents (`MDFORMAT_TARGETS`) and verify all internal/external links resolve successfully. +- **Expected Outputs & Locations**: + - **Python**: Outputs `coverage/python/coverage_tests-e2e.md`. + - **OCI**: Outputs Container Structure Test results to the console. + - **Project**: Outputs link-checking validation summaries to the console. -```bash -# 1. Install uv and hatch globally (if not already installed) -curl -LsSf https://astral.sh/uv/install.sh | sh -uv tool install hatch +#### Test Categorization & Custom Markers -# 2. Optionally, set up a Python virtual environment -uv venv -source .venv/bin/activate +To manage test execution speed and pipeline efficiency, every Python test must be decorated with one of our three custom pytest markers. These markers directly govern how frequently and in which environments those tests are executed in CI/CD pipelines. + +##### Test Categories -# 3. Sync the development environment (installs all dependency groups and extras) -uv sync --all-groups --all-extras +- **`@pytest.mark.smoke`**: + - **Encapsulation & Scope**: Extremely fast, non-flaky, critical-path verification checks. These confirm that the fundamental, basic logic of the application functions correctly (e.g., orchestrator bootstrap, CLI command recognition). + - **Execution Frequency**: Run on **every commit and Pull Request** (e.g., `development.yml`) as a quick health gate. +- **`@pytest.mark.sanity`**: + - **Encapsulation & Scope**: Detailed, comprehensive tests of core system behaviors, APIs, and edge cases. These verify that the main business logic functions robustly but may take slightly longer than smoke tests. + - **Execution Frequency**: Run on **pushes to the main branch** (e.g., `main.yml`) and release branches to ensure overall stability of the codebase. +- **`@pytest.mark.regression`**: + - **Encapsulation & Scope**: Deep, system-wide, and heavy integration/E2E regression verification checks. These ensure that complex interactions, edge cases, and past bugs do not reappear. + - **Execution Frequency**: Run on **nightly, weekly, and release schedule pipelines** due to their longer execution time. -# 4. Run hatch commands directly -hatch run test:all -hatch run lint:check -``` +> [!IMPORTANT] +> **Mandatory Tagging Policy**: +> Every Python test MUST be decorated with at least one of these markers. Unmarked tests will not align with our CI scheduling patterns and may be skipped or cause checks to fail. +> +> **Pytest Registration Rule**: +> Any new custom markers must be explicitly registered under `[tool.pytest.ini_options]` in [pyproject.toml](./pyproject.toml). Unregistered markers will trigger warnings and fail CI/CD gating pipelines. -### Managing Dependencies +##### Rust Test Categories -Use `uv` to add or update dependencies efficiently: +For Rust tests, Cargo does not have native marker annotations. We achieve the same test scheduling by filtering tests based on name substrings. When invoking the Rust test suite in CI/CD via the `.github/actions/rust/tests` action: -```bash -# Add a general dependency -uv add +- `test-category: smoke` maps to running `cargo test -- smoke`, targeting tests with `smoke` in their name (e.g., `fn test_smoke_orchestrator()`). +- `test-category: sanity` maps to running `cargo test -- sanity`. +- `test-category: regression` maps to running `cargo test -- regression`. -# Add a development dependency -uv add --group dev +Always ensure that your Rust tests are named with one of these substrings if they fall under a specific category so they are correctly picked up by CI schedules. -# Add to a specific extra -uv add --optional +> [!WARNING] +> **Rust Test Substring Matching Danger**: +> Cargo's substring filter matches *any part* of a test's name. This carries a collision risk: for example, a test named `test_heavy_regression_smoke_system` will match `smoke` and execute in the smoke test pipeline. +> To prevent accidental execution of heavy tests in quick pipelines: +> +> 1. Use distinct, unambiguous suffixes or prefixes for tests (e.g. `_smoke`, `_sanity`, `_regression`) and avoid mixing these keywords. +> 1. For larger test suites, isolate tests into separate integration test binaries under the `tests/` directory (e.g. `tests/smoke.rs`, `tests/sanity.rs`, `tests/regression.rs`) and run them directly (e.g. `cargo test --test smoke`) to achieve strict isolation. -# Sync targeted groups or extras -uv sync --group dev -uv sync --extra -``` +##### Filtering Tests by Marker (Passing Arguments) -## Running Tests +Hatch dynamically passes CLI arguments through to the underlying `pytest` execution via the `{args}` placeholder configured in [pyproject.toml](./pyproject.toml). You can target specific markers using the `-m` flag. -We maintain strict testing standards. Our tests are located in the `tests/` directory and are categorized by tier. +- **Run only smoke tests**: + ```bash + hatch run python:tests-unit -m smoke + ``` +- **Run sanity and smoke tests**: + ```bash + hatch run python:tests-unit -m "sanity or smoke" + ``` +- **Exclude regression tests**: + ```bash + hatch run python:tests-func -m "not regression" + ``` +- **Orchestrate across all environments using args**: + ```bash + hatch run all:tests-func -m smoke + ``` -| Test Tier | Directory | Description | -| :-------------- | :------------------- | :----------------------------------------------------------------- | -| **Unit** | `tests/unit/` | Fast, isolated tests for individual functions and classes. | -| **Integration** | `tests/integration/` | Slower tests that verify interactions between multiple components. | -| **End-to-End** | `tests/e2e/` | Full-stack tests simulating real user workflows. | +### Documentation Workflows -Replace the commands below with those appropriate for your language stack: +Our documentation is managed as code. It includes auto-generated CLI references, compiled Rust crates API docs, and a unified project site built using **Zensical**. -```bash -# Run unit tests -hatch run test:unit +> [!NOTE] +> **Zensical Documentation Tool**: +> Zensical is a static site generator and documentation compiler configured via `zensical.toml` that integrates MkDocs and its plugin ecosystem (such as `mkdocstrings` and macros) under a simplified configuration structure. -# Run integration tests -hatch run test:integration +#### CLI Documentation Generation -# Run all tests with coverage -hatch run test:all-cov -``` +- **Tools / Methodology / Rationale**: Uses the `typer` utility to compile and output reference docs directly from the Python entrypoint `src/rustarium/__main__.py`. +- **Command**: `hatch run python:docs` +- **Expected Outputs & Locations**: A generated Markdown reference file at `.docs/cli.md`. -## Code Quality and Formatting +#### Rust API Documentation Generation -We use opinionated formatters and linters to maintain consistency: Ruff for linting/formatting and Mypy for static type checking. +- **Tools / Methodology / Rationale**: Uses `cargo doc --workspace` to construct static HTML files documenting internal Rust API structures, crates, and types. +- **Command**: `hatch run rust:docs` +- **Expected Outputs & Locations**: Static HTML documentation directory located at `.docs/rust_api`. -- **Formatters & Linters:** - ```bash - # Check for linting and formatting issues - hatch run lint:check - hatch run types:check +#### Project Website Compilation - # Auto-format code - hatch run lint:format - ``` +- **Tools / Methodology / Rationale**: Compiles the final developer documentation site via **Zensical**, incorporating the general Markdown guides, Python CLI docs, and Rust API docs. +- **Command**: `hatch run project:docs` (or `hatch run all:docs` to generate Python/Rust docs and compile project docs together) +- **Expected Outputs & Locations**: Static build files compiled to the `site/` directory. > [!TIP] -> **IDE Configuration:** We highly recommend configuring your editor (e.g., VSCode, IntelliJ) to format on save using the project's formatting tools. For VSCode, ensure you have the relevant extensions installed and check `.vscode/settings.json` if available. +> **Dynamic Coverage Report Inclusion**: +> When compiling the website locally, Zensical dynamically embeds the Python and Rust test coverage reports (extracted from `coverage/python/` and `coverage/rust/` respectively) into the final reference pages (`docs/reference/python_coverage.md` and `docs/reference/rust_coverage.md`). If the coverage reports have been generated locally, they will automatically be included in the compiled docs site. -### Pre-commit Hooks +#### Live Development Preview Server -We use [pre-commit](https://pre-commit.com/) to ensure code quality standards are met before changes are committed. This repository is configured to use our existing Hatch environments for these checks, guaranteeing consistency with CI/CD pipelines. +- **Tools / Methodology / Rationale**: Launches a hot-reloading web server to preview changes locally in real-time. +- **Command**: + - Local Project Server: `hatch run project:docs-serve` + - Global Orchestrator: `hatch run all:docs-serve` +- **Expected Outputs & Locations**: Hot-reloading site hosted locally at `http://localhost:8000`. -**Setup:** +### Build & Distribution Workflows -1. Install pre-commit globally or in your local virtual environment: +These workflows handle compiling code, bundling extension modules, and building containerized runtimes for distribution. - ```bash - uv pip install pre-commit - ``` +#### Maturin Python Package Build -1. Install the git hook scripts: +- **Tools / Methodology / Rationale**: Uses Maturin and Hatchling (configured under `[build-system]` and `[tool.maturin]` in `pyproject.toml`) to compile Rust core extension bindings (`rustarium._rust`) and bundle Python distribution wheel packages. +- **Command**: `hatch build` +- **Expected Outputs & Locations**: Built source distributions and `.whl` files output to the `dist/` directory. - ```bash - pre-commit install - ``` +#### OCI Container Image Build -**Usage:** +- **Tools / Methodology / Rationale**: Executes a Docker build to compile the multi-stage production image, tagging the result using metadata parameters. +- **Command**: `hatch run oci:build` +- **Expected Outputs & Locations**: Local Docker image compiled and tagged as `rustarium:latest` (configured via `{env:OCI_IMAGE}`). -Once installed, pre-commit will automatically run on the modified files whenever you commit. To run the hooks manually on all files: +## CI/CD Workflows -```bash -pre-commit run --all-files -``` +We maintain high quality gates using git workflows, automated reviews, and GitHub Actions pipelines. -## Git Workflow & Branching +### Version Control Standards -We follow a structured branching strategy to maintain a clean git history. +- **Tools**: Git +- **Workflow & Commands**: + - **Branching Model**: Standard branch prefixes are enforced: + - Features: `feature/short-description` + - Bugs: `bugfix/short-description` + - Docs: `docs/short-description` + - **Commit Messages**: Enforce [Conventional Commits](https://www.conventionalcommits.org/) (e.g. `feat: ...`, `fix: ...`, `docs: ...`). + - **Versioning Tags**: Release tags must follow semver format (`v*.*.*`). -1. **Branch Naming:** +### Repository Policy & Pull Requests - - Feature branches: `feature/short-description` - - Bug fixes: `bugfix/short-description` - - Documentation: `docs/short-description` +- **Tools**: GitHub Pull Requests and Review tools +- **Workflow & Commands**: + - Open a PR against the `main` branch. + - All pipeline checks must pass (Linting, static typing, security gates, unit/integration/e2e tests). + - Require review and approval from at least one core maintainer before merging. -1. **Commit Messages:** - We encourage following [Conventional Commits](https://www.conventionalcommits.org/). +### GitHub Actions Architecture - - `feat: add new user endpoint` - - `fix: resolve crash on startup` - - `docs: update developing guide` +Our pipelines use a highly modular and DRY architecture to avoid duplication of setup steps: -1. **Pull Requests:** +- **Tools**: GitHub Actions - - Push your branch to the remote repository. - - Open a Pull Request against the `main` branch. - - Ensure all CI checks (tests, linters) pass. - - Request a review from at least one core maintainer. +- **Configuration / Manifest Files**: Reusable actions under `.github/actions/...` and triggers under `.github/workflows/...` -## CI/CD Architecture +- **Python Versioning & Parameters**: -This repository uses a modular, standardized GitHub Actions architecture. Workflows are divided into core lifecycle events and reusable helper templates. + - All composite actions (Python, Rust, OCI, and Project) support an optional `python-version` parameter. + - If omitted, actions standardize on the oldest supported version (default: `"3.10"`). + - All composite actions using change detection (`dorny/paths-filter`) support a `force-run` parameter (default: `"false"`). When set to `"true"`, it bypasses path-filtering check gates and executes the steps unconditionally (used in scheduled and release workflows). -### Lifecycle Workflows +- **OCI Tools Native Execution**: -| Workflow | Trigger | Purpose | -| :---------------------------------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------- | -| **Development** (`development.yml`) | Pull Request to `main` | Runs quality gates, security audits, unit/integration tests, and builds documentation previews. Blocks merges if checks fail. | -| **Main** (`main.yml`) | Push to `main` | Post-merge validation. Runs unit/integration/e2e tests, quality/security checks, and updates the `latest` documentation. | -| **Nightly** (`nightly.yml`) | Cron (Daily 00:00 UTC) | Runs extended regression tests, alpha builds, nightly documentation deployment, and deeper security analysis. | -| **Release** (`release.yml`) | Push of `v*.*.*` tag | Performs full verification, builds immutable release packages, versioned documentation, and publishes artifacts. | -| **Weekly** (`weekly.yml`) | Cron (Sun 00:00 UTC) | Conducts dependency hygiene and full test suite regression to catch configuration drift. | + - The repository utilizes unified platform-agnostic OCI runner logic (`scripts/run_oci.py`). + - When running in CI under `.github/actions/oci/`, the actions natively install OCI scanning and linting tools (`hadolint`, `dclint`, `dockle`, `trivy`, and `container-structure-test`) on the runner. + - This native pre-installation ensures that `run_oci.py` executes these binaries directly on the host machine, bypassing the performance overhead and Docker socket mounting requirements of containerized container-in-container execution. -### PR Feedback & Cleanup + > [!WARNING] + > **Tool Version Drift Risk**: + > Running these tools natively in CI while developers run them locally via Docker fallback containers (e.g., `aquasec/trivy:latest`) can lead to version drift. To prevent the *"it passes locally but fails in CI"* issue: + > + > 1. Keep your system-installed binaries updated to match the versions used in CI workflows (defined in the OCI composite actions). + > 1. Periodically pull the latest container images locally (`docker pull aquasec/trivy:latest`) to keep Docker fallbacks in sync with CI runner environments. -- **Safe PR Commenting:** The `pr_comment.yml` workflow uses `workflow_run` to securely post CI status comments on pull requests, circumventing write permission limits on forks. -- **Environment Cleanup:** When a PR is closed or merged, `development_cleanup.yml` automatically removes ephemeral documentation environments and artifacts to maintain a clean workspace. +- **Utility Actions (`.github/actions/utility/...`)**: -### Reusable Templates + - `setup-python`: Sets up Python, and installs uv and Hatch. + - `setup-rust`: Sets up the Rust toolchain and invokes `setup-python` to establish the Hatch orchestrator. -Our pipelines rely on modular templates located in `.github/workflows/_*.yml` (e.g., `_tests.yml`, `_quality.yml`, `_docs.yml`, `_security.yml`, `_build_package.yml`). This ensures testing granularity and linting rules remain perfectly consistent across all stages of the software lifecycle. +- **Environment Actions (`.github/actions/[env]/...`)**: -## Building Documentation + - Partitioned into folders for each environment: `python`, `rust`, `oci`, and `project`. + - Inside each environment, standard actions run specific scripts: + - `quality`: Runs formatting, linting, and type checking. + - `security`: Runs dependency audits, secrets checks, and security linters. + - `tests`: Runs unit, integration, and E2E tests, accepting `test-level`, `test-category`, and `generate-coverage` inputs. + - `build`: Compiles wheels (Python), workspace crates (Rust), container images (OCI), or all elements (Project). + - `publish`: Publishes release packages to PyPI (Python) or container images to GHCR (OCI). -Our documentation is built using [MkDocs Material](https://squidfunk.github.io/mkdocs-material/). To preview documentation changes locally: +- **Workflows (`.github/workflows/...`)**: Triggered pipelines separated into: -```bash -# Hatch manages the isolated docs environment -# Serve documentation on http://127.0.0.1:8000 with hot-reload -hatch run docs:serve -``` + - **Core Pipelines**: + - `pipeline-development.yml`: PR checks (quality, security, package build, tests, and documentation previews). + - `pipeline-main.yml`: Triggered on push to `main` branch (runs full checks and deploys latest docs). + - `pipeline-nightly.yml`: Nightly regression tests, vulnerability audits, and nightly releases. + - `pipeline-release.yml`: Release tag pushes (`v*.*.*`); packages binary builds, attests them, publishes to PyPI and GHCR, and creates releases. + - `pipeline-weekly.yml`: Scheduled weekly checks to verify environment health. + - **Utility Workflows**: + - `util-development-cleanup.yml`: Cleans up transient PR doc deployments. + - `util-pr-comment.yml`: Securely posts PR comments (build status, compiled coverage summary, documentation previews, and build packages) to avoid fork permission limits. + +### Local Workflow Testing with `act` + +You can test and validate GitHub Actions workflows locally on your development machine using [`nektos/act`](https://github.com/nektos/act). This ensures that workflows run correctly before you push changes to GitHub. + +#### Prerequisites + +1. Install **Docker** (required by `act` to spin up runner containers). +1. Install `act` using your package manager: + - macOS (Homebrew): `brew install act` + - Linux (curl): `curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash` + +> [!IMPORTANT] +> **Apple Silicon (M-series Chips) Emulation**: +> If you are on an Apple Silicon Mac, you must specify the target execution architecture using `--container-architecture linux/amd64`. This ensures that `act` pulls the `amd64` container image and installs pre-compiled `x86_64` wheels (such as `taplo`), bypassing compile-from-source errors due to missing arm64 wheels. + +#### Running Workflows Locally + +Run `act` from the repository root: + +- **List all jobs**: + + ```bash + act -l + ``` + +- **Run the default (pull_request) event (runs Development Pipeline)**: + + ```bash + act pull_request + ``` + +- **Run a specific job (e.g., project-quality)**: + + ```bash + act -j project-quality + ``` + +- **Dry-run a workflow (displays steps without execution)**: + + ```bash + act -n + ``` + +#### Mocking Event Payloads (Change Detection) + +Because composite actions use `dorny/paths-filter` to detect path-level changes, running `act` directly will fail if the required event metadata is missing. You can provide a mock payload (`event.json`) to simulate the GitHub event context: + +1. Create an `event.json` in the root of the repository: + ```json + { + "repository": { + "default_branch": "main" + } + } + ``` +1. Pass the payload file using the `-e` flag: + ```bash + act push -W .github/workflows/pipeline-main.yml -j project-quality -e event.json --container-architecture linux/amd64 + ``` + +> [!NOTE] +> `act` runs steps inside Docker containers that simulate GitHub environments. By default, it uses a medium-sized Ubuntu image, but you can specify a fuller image using `act -P ubuntu-latest=catthehacker/ubuntu:act-latest`. + +### Security & Code Scanning Gates + +- **Tools**: `detect-secrets` (secret scanning), `checkov` (infrastructure auditing), `semgrep` (semantic scanning), `pip-audit` (Python package audits), `cargo-audit` / `cargo-deny` (Rust crate audits), and `trivy` / `dockle` (OCI image scanning). +- **Workflow & Rationale**: + - **Secret Gating**: `detect-secrets` runs locally and in PR gates against the committed `.detect-secrets.scan.json` baseline to prevent credential leaks. + - **Static Analysis & CVE Auditing**: Semgrep, Trivy, Checkov, and the Cargo/pip audits run automatically as background checks on every pull request to guarantee compliance with our security baseline. + +______________________________________________________________________ -For further assistance, please refer to our [SUPPORT.md](SUPPORT.md). +For additional assistance, please refer to our [SUPPORT.md](SUPPORT.md). diff --git a/Dockerfile b/Dockerfile index 87c6335..7973ef2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,51 +1,88 @@ -# --------------------------------------------------------- -# rustarium Dockerfile -# Licensed under the Apache License, Version 2.0 -# --------------------------------------------------------- -# Standardized Multi-stage Dockerfile Template for Python - -# --------------------------------------------------------- -# Build Stage -# --------------------------------------------------------- -FROM python:3.10-slim AS builder +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# =================================================================------------- +# Stage 1: Build Stage (Python + Rust) +# =================================================================------------- +FROM python:3.10-slim-bookworm AS builder # Set working directory WORKDIR /app -# Install system build dependencies (if any) +# Install system dependencies for Rust compilation and Python build steps +# hadolint ignore=DL3008 RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential ca-certificates \ + build-essential \ + ca-certificates \ + curl \ + git \ + libssl-dev \ + pkg-config \ && rm -rf /var/lib/apt/lists/* -# Install uv for fast dependency management -RUN pip install uv hatch +# Install Rust toolchain via rustup +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH +# hadolint ignore=DL4006 +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal + +# Install Python packaging tools +# hadolint ignore=DL3013 +RUN pip install --no-cache-dir uv hatch maturin gitversioned -# Copy package manifests and install dependencies -COPY pyproject.toml README.md ./ -# Create a dummy src directory to satisfy hatch build if needed -RUN mkdir -p src/rustarium && touch src/rustarium/__init__.py -RUN uv pip install --system --no-cache -e . +ARG VERSION=0.1.0 -# Copy application source code +# Copy package manifests (utilizing optional copy for Cargo files to be robust) +COPY pyproject.toml README.md LICENSE NOTICE ./ +COPY Cargo.to[m]l Cargo.lo[c]k ./ +COPY crates/ ./crates/ +COPY scripts/ ./scripts/ + +# Copy source tree COPY src/ ./src/ -# --------------------------------------------------------- -# Build any required distribution artifacts -# --------------------------------------------------------- -RUN hatch build -# --------------------------------------------------------- -# Runtime Stage -# --------------------------------------------------------- -FROM python:3.10-slim +# Compile Rust libraries (if applicable) and build Python wheel distributions +# hadolint ignore=DL3059 +RUN hatch build -# OCI Standard Labels -LABEL org.opencontainers.image.title="rustarium" -LABEL org.opencontainers.image.description="Production-ready Python application container" -LABEL org.opencontainers.image.licenses="Apache-2.0" -LABEL org.opencontainers.image.source="https://github.com/markurtz/rustarium" -# Define environment variables +# =================================================================------------- +# Stage 2: Runtime Stage (Minimal & Secure) +# =================================================================------------- +FROM python:3.10-slim-bookworm + +# Define standard OCI build parameters +ARG BUILD_DATE +ARG GIT_SHA +ARG VERSION=0.1.0 + +# OCI Metadata Labels +LABEL org.opencontainers.image.created=$BUILD_DATE \ + org.opencontainers.image.authors="markurtz" \ + org.opencontainers.image.url="https://github.com/markurtz/rustarium" \ + org.opencontainers.image.documentation="https://markurtz.github.io/rustarium/" \ + org.opencontainers.image.source="https://github.com/markurtz/rustarium" \ + org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.revision=$GIT_SHA \ + org.opencontainers.image.vendor="markurtz" \ + org.opencontainers.image.licenses="Apache-2.0" \ + org.opencontainers.image.title="rustarium" \ + org.opencontainers.image.description="High-performance telemetrized process orchestrator and sandbox for Python and WASM" + +# Define standard production runtime environment variables ENV APP_ENV=production \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 @@ -53,19 +90,26 @@ ENV APP_ENV=production \ # Set working directory WORKDIR /app -# Create a non-root user and set permissions -RUN useradd -m appuser && chown -R appuser /app +# Create a secure, non-root system user and home directory +RUN groupadd -g 10001 appgroup && \ + useradd -u 10001 -g appgroup -m -d /home/appuser -s /sbin/nologin appuser -# Install runtime dependencies (or copy from builder if using a virtualenv) -# For simplicity, we copy the project and install it +# Copy sdist/wheel package from builder and install it COPY --from=builder /app/dist/*.whl ./ -RUN pip install *.whl && rm *.whl +# hadolint ignore=DL3013,SC2035 +RUN pip install --no-cache-dir *.whl && rm -- *.whl + +# Change ownership of the runtime directory to appuser +RUN chown -R appuser:appgroup /app -# Switch to the non-root user for security +# Switch to the secure non-root user USER appuser -# Expose necessary ports +# Expose production port EXPOSE 8080 -# Define the command to run the application +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -m rustarium --version || exit 1 + +# Run the installed application entrypoint CMD ["python", "-m", "rustarium"] diff --git a/README.md b/README.md index 158c251..d48a586 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,6 @@
- - Closed Issues - Open Issues @@ -82,15 +79,15 @@ Coming soon! ## Core Concepts -This project is built using modern Python tooling, enforcing strict code quality standards with Ruff and Mypy, and providing a robust Pydantic-driven settings architecture for configuration resolution. +This project is built using modern Python tooling, enforcing strict code quality standards with Ruff and Astral's ty, and providing a robust Pydantic-driven settings architecture for configuration resolution. ### Component Architecture The repository is structured to separate documentation, application logic, and testing cleanly: - `src/rustarium/`: The primary application source code. -- `tests/`: Comprehensive test suite ensuring reliability, organized into `unit/`, `integration/`, and `e2e/`. -- `docs/`: Source code for the MkDocs Material documentation site, including step-by-step guides, references, and getting started tutorials. +- `tests/`: Comprehensive test suite ensuring reliability, organized into `python/unit/`, `python/integration/`, and `e2e/`. +- `docs/`: Source code for the Zensical documentation site, including step-by-step guides, references, and getting started tutorials. - `examples/`: Runnable reference projects demonstrating real-world configurations. - `.github/workflows/`: Advanced CI/CD pipelines governing the project lifecycle, built around reusable workflow templates. diff --git a/covgate.toml b/covgate.toml new file mode 100644 index 0000000..2acc282 --- /dev/null +++ b/covgate.toml @@ -0,0 +1,3 @@ +[[gates]] +name = "global" +fail-under-lines = 0 diff --git a/crates/rustarium-core/Cargo.toml b/crates/rustarium-core/Cargo.toml new file mode 100644 index 0000000..cf15fa8 --- /dev/null +++ b/crates/rustarium-core/Cargo.toml @@ -0,0 +1,38 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[package] +name = "rustarium-core" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +description = "Core Rust process orchestrator and sandbox library for Python and WASM." + +[lib] +name = "rustarium_core" +crate-type = ["cdylib", "rlib"] + +[dependencies] +pyo3 = { workspace = true } +tokio = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +bincode = { workspace = true } + +[features] +extension-module = ["pyo3/extension-module"] +default = [] + + diff --git a/crates/rustarium-core/src/lib.rs b/crates/rustarium-core/src/lib.rs new file mode 100644 index 0000000..195a18a --- /dev/null +++ b/crates/rustarium-core/src/lib.rs @@ -0,0 +1,33 @@ +// Copyright 2026 markurtz +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Core Python bindings for rustarium. + +use pyo3::prelude::*; + +pub mod sum; +pub use sum::sum_as_string_internal; + +/// Sums two integers and returns the result as a string. +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult { + sum::sum_as_string_internal(a, b) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + Ok(()) +} diff --git a/crates/rustarium-core/src/sum.rs b/crates/rustarium-core/src/sum.rs new file mode 100644 index 0000000..74e4dec --- /dev/null +++ b/crates/rustarium-core/src/sum.rs @@ -0,0 +1,33 @@ +// Copyright 2026 markurtz +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Example module implementing sum logic and unit tests. + +use pyo3::prelude::*; + +/// Sums two integers and returns the result as a string. +pub fn sum_as_string_internal(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sum_as_string_internal() { + let res = sum_as_string_internal(3, 4).unwrap(); + assert_eq!(res, "7"); + } +} diff --git a/crates/rustarium-core/tests/test_integration.rs b/crates/rustarium-core/tests/test_integration.rs new file mode 100644 index 0000000..88226b9 --- /dev/null +++ b/crates/rustarium-core/tests/test_integration.rs @@ -0,0 +1,21 @@ +// Copyright 2026 markurtz +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use rustarium_core::sum_as_string_internal; + +#[test] +fn test_integration_sum() { + let result = sum_as_string_internal(10, 20).unwrap(); + assert_eq!(result, "30"); +} diff --git a/cst.yaml b/cst.yaml new file mode 100644 index 0000000..42ccb90 --- /dev/null +++ b/cst.yaml @@ -0,0 +1,44 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +schemaVersion: "2.0.0" +metadataTest: + entrypoint: [] + cmd: + - "python" + - "-m" + - "rustarium" + workdir: "/app" + exposedPorts: + - "8080" + user: "appuser" +fileExistenceTests: + - name: "Verify app directory exists" + path: "/app" + shouldExist: true +commandTests: + - name: "Verify python is installed and functional" + command: "python" + args: + - "--version" + expectedOutput: + - "Python 3.*" + - name: "Verify rustarium module execution and version check" + command: "python" + args: + - "-m" + - "rustarium" + - "--version" + expectedOutput: + - "rustarium v.*" diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..c364bea --- /dev/null +++ b/deny.toml @@ -0,0 +1,242 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + "RUSTSEC-2025-0141", # bincode is unmaintained +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "Unicode-3.0", + "Unicode-DFS-2016", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "CC0-1.0", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# If true, workspace members are automatically allowed even when using deny-by-default +# This is useful for organizations that want to deny all external dependencies by default +# but allow their own workspace crates without having to explicitly list them +allow-workspace = false +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/docker-compose.yml b/docker-compose.yml index d89dbab..88555f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,39 +1,19 @@ --- -# --------------------------------------------------------- -# rustarium Docker Compose -# Licensed under the Apache License, Version 2.0 -# --------------------------------------------------------- +name: rustarium services: app: build: context: . target: builder - ports: - - "${APP_PORT:-8080}:8080" volumes: - ./src:/app/src environment: - APP_ENV=${APP_ENV:-development} - APP_DEBUG=${APP_DEBUG:-true} + ports: + - '127.0.0.1:${APP_PORT:-8080}:8080' # Standard Python development command command: - "python" - "-m" - "rustarium" - - # Standard Database Profile - # Use `docker-compose --profile db up` to start this service - db: - image: postgres:15-alpine - profiles: - - db - ports: - - "5432:5432" - environment: - POSTGRES_USER: ${DB_USER:-admin} - POSTGRES_PASSWORD: ${DB_PASSWORD:-secret} - POSTGRES_DB: ${DB_NAME:-project_name_dev} - volumes: - - db_data:/var/lib/postgresql/data -volumes: - db_data: diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 3abe024..e043e2c 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -39,11 +39,11 @@ Ensure everything is configured correctly by running the pre-configured quality ```bash # Run linting and type checking -hatch run lint:check -hatch run types:check +hatch run all:lint +hatch run all:types # Run tests -hatch run test:all +hatch run all:tests ``` If all tests pass, your new project is successfully bootstrapped and ready for development! @@ -55,8 +55,7 @@ If all tests pass, your new project is successfully bootstrapped and ready for d Now that your project is ready, explore what `rustarium` provides: -- **[Repository Setup](../guides/repository-setup.md)** — Complete your GitHub configuration (Actions, Discussions, PyPI publishing). - **[Guides](../guides/index.md)** — Task-specific deep dives including CI/CD workflows. - **[Reference](../reference/index.md)** — Full configuration reference. -**Next:** [Repository Setup →](../guides/repository-setup.md) +**Next:** [Guides →](../guides/index.md) diff --git a/docs/guides/github-workflows.md b/docs/guides/github-workflows.md index d629b91..f468147 100644 --- a/docs/guides/github-workflows.md +++ b/docs/guides/github-workflows.md @@ -18,7 +18,7 @@ When you open a Pull Request against `main`, the `development.yml` workflow is t **What it enables:** -- **Quality Gates:** Runs `hatch run lint:check` (Ruff/mdformat) and `hatch run types:check` (Mypy) to enforce consistent formatting and type safety. +- **Quality Gates:** Runs `hatch run all:lint` (Ruff/mdformat/etc.) and `hatch run all:types` (Astral's ty/cargo check) to enforce consistent formatting and type safety. - **Security Audits:** Scans for vulnerabilities in dependencies and code patterns. - **Testing:** Runs the unit and integration test suites. The PR will be blocked from merging if any tests fail. - **Documentation Previews:** Verifies that the documentation can be built cleanly. @@ -62,7 +62,7 @@ To maintain consistency and reduce duplication, our lifecycle pipelines rely on - `_tests.yml`: Orchestrates test suite execution (unit, integration, e2e). - `_quality.yml`: Defines the exact linting and type-checking commands. - `_security.yml`: Configures dependency scanning and SAST tooling. -- `_docs.yml`: Handles building MkDocs. +- `_docs.yml`: Handles building documentation using Zensical. - `_build_package.yml`: Standardizes artifact generation. By relying on templates, we ensure that the exact same linting rules are applied whether you are testing a PR, running a nightly build, or deploying a release. @@ -72,4 +72,3 @@ ______________________________________________________________________ **Next Steps:** - See the [Contributing Guide](../community/contributing.md) for details on how to set up your local environment. -- Review the [Repository Setup](repository-setup.md) guide if you are the maintainer configuring these workflows for the first time. diff --git a/docs/guides/index.md b/docs/guides/index.md index 6b693df..8d87bdc 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -6,18 +6,6 @@ This section contains task-oriented how-to guides for `rustarium`. Unlike the [G
-
-:material-cog-outline: **Repository Setup** - -______________________________________________________________________ - -Essential configuration steps for maintainers who just instantiated this template. -Includes GitHub settings, publishing, and docs setup. - -[:octicons-arrow-right-24: Repository Setup Guide](repository-setup.md) - -
-
:material-transit-connection-variant: **CI/CD & GitHub Workflows** diff --git a/docs/guides/repository-setup.md b/docs/guides/repository-setup.md deleted file mode 100644 index 8ec7253..0000000 --- a/docs/guides/repository-setup.md +++ /dev/null @@ -1,94 +0,0 @@ -# Setting up the repository - -Welcome to your new repository! If you have just instantiated this project from the template, there are a few configuration steps required to get your standalone project fully operational. - -This guide acts as a checklist for the project maintainer. Once these steps are complete, your CI/CD pipelines, documentation site, and release workflows will be fully enabled. - -## 1. Bootstrap the repository - -Before making any manual changes, run the included interactive bootstrap script to automatically replace all placeholder variables (like `rustarium` and `markurtz`) with your actual project details. - -```bash -uv run scripts/bootstrap.py -``` - -The script will interactively prompt you for your project details (organization, project name, descriptions, and feature toggles) and automatically configure the repository. - -> [!NOTE] -> This script will rename the source directory (`src/rustarium/` -> `src/my_cool_project/`) and update all references in the documentation, GitHub Actions workflows, and configuration files. - -Once you have verified the changes and chosen to finalize the bootstrap process, the script will automatically delete itself and its related end-to-end tests. Commit the updates. - -## 2. Configure GitHub settings - -To ensure code quality and security, configure the following settings in your repository: - -1. **Enable Discussions (Optional but recommended):** - Go to **Settings > General > Features** and check **Discussions**. - -1. **Branch Protection Rules:** - Go to **Settings > Branches** and add a protection rule for `main`. - - - Check **Require a pull request before merging**. - - Check **Require status checks to pass before merging** (Require the `main` or `build` job from your CI workflow). - - Check **Do not allow bypassing the above settings**. - -1. **Tag Protection Rules:** - Go to **Settings > Tags** and add a protection rule for `v*` tags to ensure only authorized maintainers can push release tags. - -## 3. Enable documentation hosting - -This template uses MkDocs Material to generate a beautiful documentation site. The site is automatically built and deployed to the `gh-pages` branch by the `.github/workflows/pages.yml` workflow when changes are merged into `main`. - -To enable hosting: - -1. Go to **Settings > Pages**. -1. Under **Build and deployment**, set the **Source** to **Deploy from a branch**. -1. Set the **Branch** to `gh-pages` and the folder to `/ (root)`. -1. Click **Save**. - -> [!TIP] -> The `gh-pages` branch will be created automatically the first time the `pages.yml` workflow runs successfully. If it doesn't exist yet, push a change to `main` to trigger the build, then return to the Pages settings to configure it. - -## 4. Configure PyPI trusted publishing - -The template includes a `.github/workflows/release.yml` workflow that automatically publishes your package to PyPI when a release is created. This workflow uses **Trusted Publishing (OIDC)**, which is more secure than using API tokens. - -To configure Trusted Publishing: - -1. Go to [PyPI](https://pypi.org/) and navigate to your account settings. -1. Under **Publishing**, add a new **Pending Publisher**. -1. Set the **GitHub repository owner** to your organization or username. -1. Set the **GitHub repository name** to your project name. -1. Set the **Workflow name** to `release.yml`. -1. Set the **Environment name** to `pypi` (or leave it blank if not using an environment in your workflow, though the template defaults to a `pypi` environment). - -Once the first release is published, the publisher will become active. - -## 5. Enable container builds - -If your project includes a `Dockerfile` and you want to publish container images to the GitHub Container Registry (GHCR), the template workflows are already prepared. - -1. Ensure the `GITHUB_TOKEN` has `write` permissions for packages. Go to **Settings > Actions > General**. -1. Under **Workflow permissions**, ensure it is set to **Read and write permissions**. -1. When the `release.yml` or `nightly.yml` workflows run, they will build and push the container image to `ghcr.io/YOUR_ORG/YOUR_PROJECT`. - -You may also want to make the package public once it is published by going to your Organization's **Packages** settings. - -## 6. Manage versions and releases - -This template is designed to use Git tags for versioning (e.g., via `hatch-vcs` or a similar dynamic versioning tool). You do not need to hardcode the version number in `pyproject.toml` or `__init__.py`. - -To cut a new release: - -1. **Tag the commit:** Create an annotated tag for your release (e.g., `v1.0.0`). - ```bash - git tag -a v1.0.0 -m "Release v1.0.0" - git push origin v1.0.0 - ``` -1. **Create a GitHub Release:** Go to the repository's **Releases** page and draft a new release using the tag you just pushed. -1. **Automated Publishing:** Creating the release will trigger the `.github/workflows/release.yml` workflow, which will build the package, attest its provenance, and publish it to PyPI and GHCR. - -______________________________________________________________________ - -**Next:** Now that your repository is configured, try running through the [Quick Start](../getting-started/quickstart.md) to verify everything is working locally! diff --git a/docs/reference/cli.md b/docs/reference/cli.md new file mode 100644 index 0000000..042a6e2 --- /dev/null +++ b/docs/reference/cli.md @@ -0,0 +1,5 @@ +# CLI + +This page documents the CLI for `rustarium`. + +{{ include_file_or_placeholder('.docs/cli.md', '\n*The CLI documentation has not been generated yet. Run `hatch run python:docs` to include it in the build.*\n') }} diff --git a/docs/reference/index.md b/docs/reference/index.md index a393616..9a04c2d 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,34 +1,30 @@ # Reference -The Reference section contains the complete technical documentation for rustarium — API classes and configuration options. This is the section to bookmark when you need to look something up. +The Reference section contains the complete technical documentation for `rustarium` — API classes, configuration options, CLI reference, and test coverage reports. ## In This Section
-
-:material-code-json: **API Reference** +- **[:material-console: CLI](cli.md)** -______________________________________________________________________ + Command-line usage and subcommands. -Auto-generated documentation for all public classes, methods, and modules. +- **[:material-api: Python API](python_api.md)** -[:octicons-arrow-right-24: Python API Reference](api/rustarium/) + Complete public Python API documentation. -
+- **[:material-api: Rust API](rust_api.md)** -
-:material-test-tube: **Test Coverage** + Rust crates and core API references. -______________________________________________________________________ +- **[:material-shield-check: Python Tests](python_coverage.md)** -Run `hatch run test:all-cov` to generate HTML reports locally, or view the latest pipeline runs: + Python coverage reports for unit, integration, functional, and E2E tests. -- [:octicons-arrow-right-24: Unit Tests](../coverage/unit/htmlcov/index.html) -- [:octicons-arrow-right-24: Integration Tests](../coverage/integration/htmlcov/index.html) -- [:octicons-arrow-right-24: End-to-End Tests](../coverage/e2e/htmlcov/index.html) +- **[:material-shield-check: Rust Tests](rust_coverage.md)** -
+ Rust coverage reports for unit, integration, and functional tests.
@@ -49,15 +45,3 @@ settings = Settings(environment="production") # Log the current configuration logger.info("Application initialized with settings: {}", settings) ``` - -## Generating Reference Docs - -This project is pre-configured to work with [`mkdocstrings`](https://mkdocstrings.github.io/) for auto-generating API documentation from docstrings. - -### Setup - -> [!NOTE] -> `mkdocstrings` supports multiple languages via handlers (e.g., Python, C++, Crystal). See the [mkdocstrings documentation](https://mkdocstrings.github.io/) to configure it for your language. - -1. The plugin and its Python handler are already managed by Hatch in the `docs` environment (`mkdocstrings[python]`). -1. Configure `mkdocs.yml` according to your language handler and create your reference pages. diff --git a/docs/reference/python_api.md b/docs/reference/python_api.md new file mode 100644 index 0000000..2bdaa98 --- /dev/null +++ b/docs/reference/python_api.md @@ -0,0 +1,5 @@ +# Python API + +This page documents the public Python API for `rustarium`. + +::: rustarium diff --git a/docs/reference/python_coverage.md b/docs/reference/python_coverage.md new file mode 100644 index 0000000..d245945 --- /dev/null +++ b/docs/reference/python_coverage.md @@ -0,0 +1,15 @@ +# Python Tests + +This page displays the coverage reports for various Python test suites. + +## Functional Test Coverage +{{ include_file_or_placeholder('coverage/python/coverage_tests-func.md', '\n*No functional test coverage report has been generated yet. Run `hatch run python:tests-func-cov` to generate it.*\n') }} + +## Unit Test Coverage +{{ include_file_or_placeholder('coverage/python/coverage_tests-unit.md', '\n*No unit test coverage report has been generated yet. Run `hatch run python:tests-unit-cov` to generate it.*\n') }} + +## Integration Test Coverage +{{ include_file_or_placeholder('coverage/python/coverage_tests-int.md', '\n*No integration test coverage report has been generated yet. Run `hatch run python:tests-int-cov` to generate it.*\n') }} + +## End-to-End Test Coverage +{{ include_file_or_placeholder('coverage/python/coverage_tests-e2e.md', '\n*No end-to-end test coverage report has been generated yet. Run `hatch run python:tests-e2e-cov` to generate it.*\n') }} diff --git a/docs/reference/rust_api.md b/docs/reference/rust_api.md new file mode 100644 index 0000000..5e1812b --- /dev/null +++ b/docs/reference/rust_api.md @@ -0,0 +1,3 @@ +# Rust API + +{{ include_rust_api_reference() }} diff --git a/docs/reference/rust_coverage.md b/docs/reference/rust_coverage.md new file mode 100644 index 0000000..c012587 --- /dev/null +++ b/docs/reference/rust_coverage.md @@ -0,0 +1,15 @@ +# Rust Tests + +This page displays the coverage reports for various Rust test suites. + +## Functional Test Coverage +{{ include_file_or_placeholder('coverage/rust/coverage_tests-func.md', '\n*No functional test coverage report has been generated yet. Run `hatch run rust:tests-func-cov` to generate it.*\n') }} + +## Unit Test Coverage +{{ include_file_or_placeholder('coverage/rust/coverage_tests-unit.md', '\n*No unit test coverage report has been generated yet. Run `hatch run rust:tests-unit-cov` to generate it.*\n') }} + +## Integration Test Coverage +{{ include_file_or_placeholder('coverage/rust/coverage_tests-int.md', '\n*No integration test coverage report has been generated yet. Run `hatch run rust:tests-int-cov` to generate it.*\n') }} + +## End-to-End Test Coverage +{{ include_file_or_placeholder('coverage/rust/coverage_tests-e2e.md', '\n*No end-to-end test coverage report is applicable for the Rust environment.*\n') }} diff --git a/docs/scripts/extra.js b/docs/scripts/extra.js deleted file mode 100644 index 5011454..0000000 --- a/docs/scripts/extra.js +++ /dev/null @@ -1 +0,0 @@ -/* Custom JavaScript for MkDocs */ diff --git a/docs/scripts/gen_ref_pages.py b/docs/scripts/gen_ref_pages.py deleted file mode 100644 index 06775e8..0000000 --- a/docs/scripts/gen_ref_pages.py +++ /dev/null @@ -1,76 +0,0 @@ -# noqa: INP001 -""" -Generate the code reference pages and navigation. - -This module automates the creation of API reference documentation. It leverages the -``mkdocs-gen-files`` plugin to traverse the ``src/`` directory, generating Markdown -pages for each Python module and constructing a unified navigation structure. This -ensures the project's codebase remains fully documented and accessible. -""" - -from pathlib import Path - -import mkdocs_gen_files - -__all__ = ["generate_api_reference"] - - -def generate_api_reference() -> None: - """ - Generate the API reference documentation and navigation structure. - - Iterates through all Python source files in the ``src/`` directory, converting - their paths into a logical documentation structure. It generates Markdown files - with appropriate directives to render the API, and produces a literate navigation - summary. - - Example: - This function is executed directly when the script is run: - - .. code-block:: python - - generate_api_reference() - - :return: None - """ - navigation = mkdocs_gen_files.Nav() - root_directory = Path(__file__).parent.parent.parent - source_directory = root_directory / "src" - api_reference_path = Path("reference/api") - - for python_file in sorted(source_directory.rglob("*.py")): - module_path = python_file.relative_to(source_directory).with_suffix("") - document_path = python_file.relative_to(source_directory).with_suffix(".md") - full_document_path = api_reference_path / document_path - - path_parts = tuple(module_path.parts) - - if path_parts[-1] == "__init__": - path_parts = path_parts[:-1] - document_path = document_path.with_name("index.md") - full_document_path = full_document_path.with_name("index.md") - elif path_parts[-1] == "__main__": - continue - - if not path_parts: - continue - - navigation[path_parts] = document_path.as_posix() - - with mkdocs_gen_files.open(full_document_path, "w") as document_file: - identifier = ".".join(path_parts) - document_file.write( - f"::: {identifier}\n" - " options:\n" - " show_root_heading: true\n" - " show_source: true\n" - ) - - mkdocs_gen_files.set_edit_path(full_document_path, python_file) - - summary_file_path = api_reference_path / "SUMMARY.md" - with mkdocs_gen_files.open(summary_file_path, "w") as navigation_file: - navigation_file.writelines(navigation.build_literate_nav()) - - -generate_api_reference() diff --git a/docs/scripts/macros.py b/docs/scripts/macros.py new file mode 100644 index 0000000..8b90cb1 --- /dev/null +++ b/docs/scripts/macros.py @@ -0,0 +1,79 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Custom Zensical macros for markdown document processing. + +This module defines hooks to dynamically modify documentation pages +during Zensical builds, such as conditionally embedding files and +generating links to Rust API reference documentation. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Annotated, Any + +__all__: Annotated[ + list[str], + "The list of public symbols exported by this module.", +] = [ + "RUST_API_SRC", + "define_env", +] + +RUST_API_SRC: Annotated[ + Path, + "The directory path containing generated Rust API reference HTML.", +] = Path(".docs/rust_api/doc") + + +def define_env(env: Any) -> None: + """ + Register custom macros in the documentation environment. + + This function hooks into Zensical's build cycle to register dynamic markdown + macros, enabling conditional file inclusion and API documentation linking. + + Example: + >>> class MockEnv: + ... def macro(self, func): return func + >>> define_env(MockEnv()) + + :param env: The documentation environment instance used to register macros. + :return: None + """ + + @env.macro + def include_file_or_placeholder(file_path: str, placeholder: str) -> str: + target_path = Path(file_path) + if target_path.exists(): + return target_path.read_text(encoding="utf-8") + return placeholder + + @env.macro + def include_rust_api_reference() -> str: + if RUST_API_SRC.exists(): + return ( + "The Rust API documentation has been successfully generated.\n\n" + "* **[Open Rust API Reference (rustarium_core)]" + "(rust_api/rustarium_core/index.html)**\n" + ) + return ( + "The Rust API documentation is not included in this build.\n\n" + "To generate the Rust API documentation, run:\n" + "```bash\n" + "hatch run rust:docs\n" + "```\n" + ) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css deleted file mode 100644 index 1bb13e7..0000000 --- a/docs/stylesheets/extra.css +++ /dev/null @@ -1 +0,0 @@ -/* Custom Stylesheet for MkDocs */ diff --git a/llms-full.txt b/llms-full.txt index 90621dd..94a2d09 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -28,9 +28,9 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm

- - - rustarium Logo + + + rustarium Logo

@@ -43,12 +43,12 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm GitHub Release - - +
@@ -56,21 +56,16 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm
- - Closed Issues - - License

- Documentation | + Documentation | Roadmap | Issues | Discussions @@ -82,32 +77,21 @@ ______________________________________________________________________

- - - User Flow Diagram + + + User Flow Diagram

-Welcome to the rustarium template repository! This template provides a robust foundation for building high-quality, scalable software projects. It includes standard directories, issue templates, CI/CD workflows, and comprehensive placeholder documentation. - -To use this template, run `uv run scripts/bootstrap.py` to automatically configure your project with your organization and repository details, update the placeholder SVG images in `docs/assets/branding/`, and you are ready to start coding. +Welcome to the rustarium repository! ### Why Use rustarium? -- **Consistency:** Enforces a standardized layout and structure across your organization's repositories. -- **Speed:** Bootstraps your project with pre-configured Actions, badges, and templates so you don't start from scratch. -- **Best Practices:** Baked-in guides for contributing, security, and developer setup. +Coming soon! ### Comparisons -When evaluating rustarium against other templates, consider the following differences: - -| Feature | rustarium Template | Standard GitHub Init | Cookiecutter / Copier | -| :----------------- | :------------------------------- | :------------------- | :------------------------- | -| **Setup Speed** | Very Fast | Fast | Slower (requires CLI tool) | -| **Visual Assets** | Pre-configured Light/Dark assets | None | Varies | -| **CI/CD Built-in** | Yes (GitHub Actions) | No | Optional | -| **Complexity** | Low (Find and Replace) | None | Medium (Jinja templates) | +Coming soon! ## What's New @@ -119,56 +103,52 @@ This project has just been instantiated from the template repository. Keep an ey ## Quick Start -```bash -pip install rustarium -``` - -For full installation options (from source, Docker, platform-specific notes) and step-by-step onboarding, see the **[Getting Started guide](https://markurtz.github.io/rustarium/getting-started/)**. +Coming soon! ## Core Concepts -This project is built using modern Python tooling, enforcing strict code quality standards with Ruff and Mypy, and providing a robust Pydantic-driven settings architecture for configuration resolution. +This project is built using modern Python tooling, enforcing strict code quality standards with Ruff and Astral's ty, and providing a robust Pydantic-driven settings architecture for configuration resolution. ### Component Architecture The repository is structured to separate documentation, application logic, and testing cleanly: - `src/rustarium/`: The primary application source code. -- `tests/`: Comprehensive test suite ensuring reliability, organized into `unit/`, `integration/`, and `e2e/`. -- `docs/`: Source code for the MkDocs Material documentation site, including step-by-step guides, references, and getting started tutorials. +- `tests/`: Comprehensive test suite ensuring reliability, organized into `python/unit/`, `python/integration/`, and `e2e/`. +- `docs/`: Source code for the Zensical documentation site, including step-by-step guides, references, and getting started tutorials. - `examples/`: Runnable reference projects demonstrating real-world configurations. - `.github/workflows/`: Advanced CI/CD pipelines governing the project lifecycle, built around reusable workflow templates. ## Advanced Usage -Please check the [`examples/`](examples/) directory for advanced examples and configurations. +Please check the [`examples/`](https://github.com/markurtz/rustarium/tree/main/examples/) directory for advanced examples and configurations. ## General ### Contributing -We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for more details. For development setup, check out [DEVELOPING.md](DEVELOPING.md). -Please ensure you follow our [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions. +We welcome contributions! Please see our [Contributing Guide](https://github.com/markurtz/rustarium/blob/main/CONTRIBUTING.md) for more details. For development setup, check out [DEVELOPING.md](https://github.com/markurtz/rustarium/blob/main/DEVELOPING.md). +Please ensure you follow our [Code of Conduct](https://github.com/markurtz/rustarium/blob/main/CODE_OF_CONDUCT.md) in all interactions. ### Support and Security -- For help and general questions, see [SUPPORT.md](SUPPORT.md). -- To report a security vulnerability, please refer to our [Security Policy](SECURITY.md). +- For help and general questions, see [SUPPORT.md](https://github.com/markurtz/rustarium/blob/main/SUPPORT.md). +- To report a security vulnerability, please refer to our [Security Policy](https://github.com/markurtz/rustarium/blob/main/SECURITY.md). ### AI & LLM Tooling This repository includes first-class support for agentic and LLM-assisted development workflows: -- **[AGENTS.md](AGENTS.md):** Repository-specific instructions for AI coding agents (Codex, Copilot Workspace, Gemini, Claude, Cursor, and similar tools). Contains the authoritative guide for project structure, executable commands, code style, and critical constraints. -- **[llms.txt](llms.txt):** A machine-readable index of the project's documentation, following the [llms.txt specification](https://llmstxt.org/). Served at `/llms.txt` on the documentation site to help LLMs quickly locate and consume relevant content. +- **[AGENTS.md](https://github.com/markurtz/rustarium/blob/main/AGENTS.md):** Repository-specific instructions for AI coding agents (Codex, Copilot Workspace, Gemini, Claude, Cursor, and similar tools). Contains the authoritative guide for project structure, executable commands, code style, and critical constraints. +- **[llms.txt](https://github.com/markurtz/rustarium/blob/main/llms.txt):** A machine-readable index of the project's documentation, following the [llms.txt specification](https://llmstxt.org/). Served at `/llms.txt` on the documentation site to help LLMs quickly locate and consume relevant content. ### License -This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details. +This project is licensed under the Apache License 2.0. See the [LICENSE](https://github.com/markurtz/rustarium/blob/main/LICENSE) file for details. ### Citations -If you use this template or the resulting software in your research, please cite it using the following BibTeX entry: +If you use this repository or the resulting software in your research, please cite it using the following BibTeX entry: ```bibtex @software{rustarium, @@ -179,7 +159,6 @@ If you use this template or the resulting software in your research, please cite } ``` - ## File: AGENTS.md # AGENTS.md — AI Agent & Coding Assistant Guide @@ -206,17 +185,19 @@ If you use this template or the resulting software in your research, please cite ## Repository Layout - `.github/workflows/`: CI/CD pipelines. Files prefixed with `_` are reusable templates. -- `docs/`: MkDocs Material source. +- `docs/`: Zensical documentation source. - `src/`: Primary application source code. -- `tests/`: Organized into `unit/`, `integration/`, and `e2e/`. +- `tests/`: Organized into `python/unit/`, `python/integration/`, and `e2e/`. ## Executable Commands -- **Linting:** `hatch run lint:check` (Ruff & mdformat) and `hatch run types:check` (Mypy) -- **Pre-commit:** `pre-commit run --all-files` (Runs formatting and quality checks) -- **Testing:** `hatch run test:all` (Pytest) and `hatch run test:all-cov` (Coverage) -- **Docs:** `hatch run docs:serve` / `hatch run docs:build` -- **Build:** `hatch build` +- **Formatting:** `hatch run all:format` (or `hatch run python:format`, `hatch run rust:format`, `hatch run project:format`, `hatch run oci:format`) +- **Linting:** `hatch run all:lint` (cascades to all environments) or individual checks (e.g., `hatch run python:lint`, `hatch run project:lint`) +- **Type Checking:** `hatch run all:types` (cascades to python/rust check targets) or environment-specific +- **Testing:** `hatch run all:tests` (runs python + rust unit/integration and project e2e), `hatch run python:tests` (with optional `-cov` suffix) +- **Security Audits:** `hatch run all:security` or environment-specific (e.g., `hatch run python:security`) +- **Docs:** `hatch run project:serve-docs` (local dev server) / `hatch run project:build-docs` (static build using Zensical) +- **Build:** `hatch build` (Maturin Python package build) or `hatch run all:build` (builds python, OCI, docs) ## Code Style & Patterns @@ -236,222 +217,347 @@ If you use this template or the resulting software in your research, please cite ## Documentation -- **`docs/`** and **`mkdocs.yml`** control the site. Do not create docs outside the `nav:` tree. -- `docs/index.md` dynamically includes `README.md` via MkDocs snippets. +- **`docs/`** and **`zensical.toml`** control the site. - Use `{{placeholder}}` variables for templated fields (e.g., `rustarium`, `markurtz`). ## Agent Notes _Add notes here when updating instructions for AI agents._ - ## File: DEVELOPING.md # Developing `rustarium` This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards. -## Prerequisites +## Setup & Prerequisites -Ensure your system meets the following requirements before getting started: +Ensure your system meets the requirements below to establish a consistent local development environment, or utilize our containerized development setup. -- **[Docker](https://docs.docker.com/get-docker/)** (Recommended for isolated environments) -- **[Git](https://git-scm.com/)** (Version control) -- **[Python](https://www.python.org/)** 3.10+ -- **[Hatch](https://hatch.pypa.io/)** (Project manager) +### Supported Operating Systems -> [!NOTE] -> We strongly recommend using our Docker setup to ensure your local environment exactly matches our CI/CD pipelines. +- **macOS & Linux**: Standard operating systems that are fully supported, actively tested, and maintained. +- **Windows**: Not officially tested or maintained. Windows users encountering issues should use the [Development Environment Container](#development-environment-container-devcontainer) setup. -## Quick Start (Docker) +### Development Environment Container (.devcontainer) -> [!IMPORTANT] -> The `Dockerfile` and `docker-compose.yml` files included in this template are **placeholders** that must be filled in for your specific language stack before they are usable. The default `CMD` in both files will exit with an error if run without modification. -> -> Once implemented, you can spin up the development environment with: -> -> ```bash -> git clone https://github.com/markurtz/rustarium.git -> cd rustarium -> -> # Build and start the development environment in the background -> docker-compose up -d --build -> ``` +- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **Dev Containers** extension installed. +- **Usage**: + 1. Clone this repository: `git clone https://github.com/markurtz/rustarium.git` + 1. Open the project folder in VS Code. + 1. A prompt will appear: "Reopen in Container". Click it to launch the environment. + 1. VS Code will build the container, install the stable Rust toolchain, and automatically run `uv sync --all-groups --all-extras` to install and sync the Python environment. -To view the logs of your running containers: +### Local Setup + +- **[Git](https://git-scm.com/)**: Version control tool. Refer to the [Git Documentation](https://git-scm.com/doc) for installation instructions. +- **[Docker](https://www.docker.com/)**: Container management system. Install via the [Docker Installation Guide](https://docs.docker.com/get-docker/). +- **[Python](https://www.python.org/) 3.10 - 3.14**: Core runtime environment. Install via the [Python Downloads Page](https://www.python.org/downloads/). +- **[Rust & rustup](https://rustup.rs/)**: Stable compiler toolchain. Install via the [Rustup Installer](https://rustup.rs/). +- **[uv](https://docs.astral.sh/uv/)**: Fast package installer and resolver. Install via the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/). +- **[Hatch](https://hatch.pypa.io/)**: Project workflow orchestrator. Install via the [Hatch installation guide](https://hatch.pypa.io/latest/install/). + +## Developer Quickstart + +Once your environment is set up (either via the Dev Container or manually), follow this self-contained workflow for a complete end-to-end development cycle: + +### 1. Branching & Changes + +Create a new feature branch and make your changes across Python, Rust, or OCI files: ```bash -docker-compose logs -f +git checkout -b feat/my-contribution ``` -## Local Setup - -If you prefer to develop directly on your host machine, this project uses [uv](https://docs.astral.sh/uv/) for environment management and dependency resolution, alongside [Hatch](https://hatch.pypa.io/) as our command orchestrator. +### 2. Formatting & Linting -> [!NOTE] -> **A note on shared tooling:** This project uses [MkDocs](https://www.mkdocs.org/) for documentation. +Run all formatters and lint checks across the codebase to verify code quality and compliance: ```bash -# 1. Install uv and hatch globally (if not already installed) -curl -LsSf https://astral.sh/uv/install.sh | sh -uv tool install hatch +# Auto-format codebase styles and rules +hatch run all:format # Cascades to format project, python, rust, and oci files -# 2. Optionally, set up a Python virtual environment -uv venv -source .venv/bin/activate +# Run read-only quality gating validation +hatch run all:lint # Cascades to lint project, python, rust, and oci files +``` -# 3. Sync the development environment (installs all dependency groups and extras) -uv sync --all-groups --all-extras +### 3. Static Type Analysis -# 4. Run hatch commands directly -hatch run test:all -hatch run lint:check +Validate that types resolve correctly across languages: + +```bash +# Verify static types across Python and Rust +hatch run all:types ``` -### Managing Dependencies +### 4. Unit & Integration Testing -Use `uv` to add or update dependencies efficiently: +Verify component-level functionality, correctness, and coverage: ```bash -# Add a general dependency -uv add +# Python unit and integration tests with coverage tracking +hatch run python:tests-cov + +# Rust unit and integration tests +hatch run rust:tests -# Add a development dependency -uv add --group dev +# OCI Container Structure Tests (CST) +hatch run oci:tests +``` + +### 5. System End-to-End (e2e) Testing -# Add to a specific extra -uv add --optional +Run the complete system-level black-box integration test suite: -# Sync targeted groups or extras -uv sync --group dev -uv sync --extra +```bash +hatch run project:tests-e2e +# Or run the entire test suite (unit + integration + e2e) +hatch run all:tests ``` -## Running Tests +### 6. Local Compilation & Manual Verification -We maintain strict testing standards. Our tests are located in the `tests/` directory and are categorized by tier. +Build the projects and spin them up locally to verify they run cleanly: -| Test Tier | Directory | Description | -| :-------------- | :------------------- | :----------------------------------------------------------------- | -| **Unit** | `tests/unit/` | Fast, isolated tests for individual functions and classes. | -| **Integration** | `tests/integration/` | Slower tests that verify interactions between multiple components. | -| **End-to-End** | `tests/e2e/` | Full-stack tests simulating real user workflows. | +```bash +# Build Python wheels and compile Rust bindings via Maturin +hatch build + +# Build and run the local container orchestrator stack +hatch run oci:build # Builds the production image +docker compose up -d # Spins up orchestrator stack +docker compose ps # Verifies running services and health checks +docker compose down # Tears down the compose environment +``` -Replace the commands below with those appropriate for your language stack: +### 7. Documentation Build Verification + +Ensure your documentation changes compile and render without errors: ```bash -# Run unit tests -hatch run test:unit +hatch run project:serve-docs +``` -# Run integration tests -hatch run test:integration +Open `http://localhost:8000` to preview the rendered documentation site. -# Run all tests with coverage -hatch run test:all-cov +### 8. Push & Pull Request + +Verify formatting and lint check gates locally before pushing your changes: + +```bash +hatch run all:lint +git push -u origin feat/my-contribution ``` -## Code Quality and Formatting +After pushing successfully, open a Pull Request (PR) on GitHub to trigger the automated CI check pipelines. -We use opinionated formatters and linters to maintain consistency: Ruff for linting/formatting and Mypy for static type checking. +## Coding Workflows -- **Formatters & Linters:** - ```bash - # Check for linting and formatting issues - hatch run lint:check - hatch run types:check +Our codebase consists of Python modules, Rust crates, and Docker orchestration tools. Follow the specific guidelines below for each pathway. - # Auto-format code - hatch run lint:format - ``` +All development commands are unified under `pyproject.toml` and managed using Hatch in the format: -> [!TIP] -> **IDE Configuration:** We highly recommend configuring your editor (e.g., VSCode, IntelliJ) to format on save using the project's formatting tools. For VSCode, ensure you have the relevant extensions installed and check `.vscode/settings.json` if available. +```bash +hatch run [ENVIRONMENT]:[COMMAND] +``` -### Pre-commit Hooks +Or you can use the global orchestrator targets: -We use [pre-commit](https://pre-commit.com/) to ensure code quality standards are met before changes are committed. This repository is configured to use our existing Hatch environments for these checks, guaranteeing consistency with CI/CD pipelines. +```bash +hatch run all:[COMMAND] +``` -**Setup:** +### Subsection 1: Quality Assurance & Analysis + +We enforce strict quality controls across the repository using automated formatting, linting, type-checking, and security scanners. + +#### 1. Code Formatting + +Auto-formats style rules across the codebase. + +- **Project level**: Formats Markdown, YAML, and TOML. + - *Tools*: `mdformat` (Markdown), `yamlfix` (YAML), `taplo` (TOML). + - *Command*: `hatch run project:format` +- **Python**: Applies import sorting and style changes. + - *Tools*: `ruff`. + - *Command*: `hatch run python:format` +- **Rust**: Enforces official Rust layout styles. + - *Tools*: `cargo fmt`. + - *Command*: `hatch run rust:format` +- **OCI**: Formats Compose and container specs. + - *Tools*: `dclint`. + - *Command*: `hatch run oci:format` +- **Global Orchestrator**: Runs all formatters sequentially. + - *Command*: `hatch run all:format` + +#### 2. Linting & Verification + +Performs read-only validation of codebase syntax and configurations. + +- **Project level**: Checks Markdown links, formatting, YAML rules, and TOML. + - *Tools*: `mdformat --check`, `yamlfix --check`, `yamllint`, `taplo check`. + - *Command*: `hatch run project:lint` +- **Python**: Validates standard PEP 8, unused imports, and style rules. + - *Tools*: `ruff check`. + - *Command*: `hatch run python:lint` +- **Rust**: Runs Clippy compiler lints as errors. + - *Tools*: `cargo clippy`. + - *Command*: `hatch run rust:lint` +- **OCI**: Validates Dockerfile syntax and compose files. + - *Tools*: `hadolint`, `docker compose config`. + - *Command*: `hatch run oci:lint` +- **Global Orchestrator**: Checks lint rules across all files. + - *Command*: `hatch run all:lint` + +#### 3. Static Type Checking + +Validates type signatures across languages. + +- **Python**: Standard type verification. + - *Tools*: Astral's `ty`. + - *Command*: `hatch run python:types` +- **Rust**: Compiles target crates to verify type safety. + - *Tools*: `cargo check`. + - *Command*: `hatch run rust:types` +- **Global Orchestrator**: Verifies Python and Rust types. + - *Command*: `hatch run all:types` + +#### 4. Security & Vulnerability Auditing + +Scans for secrets, bad dependencies, and insecure patterns. + +- **Project level**: Detects hardcoded secrets, checks IaC compliance. + - *Tools*: `detect-secrets`, `checkov`, `guarddog`. + - *Command*: `hatch run project:security` +- **Python**: Audits dependencies and code rules. + - *Tools*: `semgrep`, `pip-audit`, `ruff` security rules. + - *Command*: `hatch run python:security` +- **Rust**: Validates dependency trees for CVEs and licenses. + - *Tools*: `cargo audit`, `cargo deny`. + - *Command*: `hatch run rust:security` +- **OCI**: Scans built containers for CVEs. + - *Tools*: `trivy`, `dockle`. + - *Command*: `hatch run oci:security` +- **Global Orchestrator**: Executes security sweeps across the stack. + - *Command*: `hatch run all:security` -1. Install pre-commit globally or in your local virtual environment: +______________________________________________________________________ - ```bash - uv pip install pre-commit - ``` +### Subsection 2: Testing Strategy -1. Install the git hook scripts: +We separate testing into distinct scopes (unit, integration, and end-to-end) across different environment runtimes. - ```bash - pre-commit install - ``` +#### 1. Unit & Integration Testing -**Usage:** +Verifies isolated functions and component-level communications. -Once installed, pre-commit will automatically run on the modified files whenever you commit. To run the hooks manually on all files: +- **Python**: + - *Suite command*: `hatch run python:tests` + - *Suite with coverage*: `hatch run python:tests-cov` (generates HTML report under `docs/coverage/all/htmlcov`) + - *Unit-only*: `hatch run python:tests-unit` (coverage report generated under `docs/coverage/unit/htmlcov` via `hatch run python:tests-unit-cov`) + - *Integration-only*: `hatch run python:tests-int` (coverage report generated under `docs/coverage/integration/htmlcov` via `hatch run python:tests-int-cov`) + - *Test Markers*: We utilize `@pytest.mark.smoke` (smoke tests), `@pytest.mark.sanity` (standard sanity checks), and `@pytest.mark.regression` (regression verification). Filter tests via `-m` (e.g. `hatch run python:tests -m "smoke"`). + > [!CAUTION] + > **Pytest Custom Markers Caution**: + > Any new custom markers must be explicitly registered in `[tool.pytest.ini_options]` of `pyproject.toml`. Unregistered markers will trigger warnings and fail CI/CD checks. +- **Rust**: + - *Suite command*: `hatch run rust:tests` + - *Suite with coverage*: `hatch run rust:tests-cov` (runs `cargo llvm-cov`) + - *Unit-only*: `hatch run rust:tests-unit` (checks `lib` and `bins`) + - *Integration-only*: `hatch run rust:tests-int` (checks `tests/` directories) +- **OCI**: + - *Suite command*: `hatch run oci:tests` + - *Tools*: `container-structure-test` (verifies file placement, configuration settings, and executable commands in built images). +- **Global Orchestrator**: Runs code tests across both Python and Rust: + - *Command*: `hatch run all:tests-code` (with optional `-cov` suffix: `hatch run all:tests-code-cov`) -```bash -pre-commit run --all-files -``` +#### 2. End-to-End (e2e) Testing -## Git Workflow & Branching +Simulates real black-box orchestrator workflows in isolated system paths. -We follow a structured branching strategy to maintain a clean git history. +- **Project level**: Runs pytest against the E2E directory to confirm whole-system compliance. + - *Command*: `hatch run project:tests-e2e` +- **Global Orchestrator**: Runs e2e suite through the orchestrator. + - *Command*: `hatch run all:tests-e2e` -1. **Branch Naming:** +______________________________________________________________________ - - Feature branches: `feature/short-description` - - Bug fixes: `bugfix/short-description` - - Documentation: `docs/short-description` +### Subsection 3: Build & Distribution Pipelines -1. **Commit Messages:** - We encourage following [Conventional Commits](https://www.conventionalcommits.org/). +These pipelines compile local binaries, generate distribution packages, and compile our developer documentation. - - `feat: add new user endpoint` - - `fix: resolve crash on startup` - - `docs: update developing guide` +#### 1. Compilation & Package Build -1. **Pull Requests:** +- **Python / Rust Bindings**: Compiles the underlying Rust extension modules and packages Python distribution wheels. + - *Tools*: `Maturin` and `hatchling` (configured under `[build-system]` and `[tool.maturin]` in `pyproject.toml`). + - *Command*: `hatch build`. +- **OCI Container Image**: Multi-stage Docker image builds. + - *Command*: `hatch run oci:build` (builds `rustarium:latest` injecting build date, git SHA, and version parameters). +- **Global Orchestrator**: Compiles and builds all package artifacts: + - *Command*: `hatch run all:build` (includes python packages, OCI image, and documentation). - - Push your branch to the remote repository. - - Open a Pull Request against the `main` branch. - - Ensure all CI checks (tests, linters) pass. - - Request a review from at least one core maintainer. +#### 2. Documentation Build & Serving -## CI/CD Architecture +We use **Zensical** to build and preview the project's developer guides, references, and release details. -This repository uses a modular, standardized GitHub Actions architecture. Workflows are divided into core lifecycle events and reusable helper templates. +- **Target configuration**: `zensical.toml` +- **Live Local Server**: Runs a hot-reloading development preview. + - *Command*: `hatch run project:serve-docs` (accessible at `http://localhost:8000`) +- **Static Assets Compilation**: Compiles the source files into HTML. + - *Command*: `hatch run project:build-docs` (outputs to the `site/` folder) -### Lifecycle Workflows +______________________________________________________________________ -| Workflow | Trigger | Purpose | -| :---------------------------------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------- | -| **Development** (`development.yml`) | Pull Request to `main` | Runs quality gates, security audits, unit/integration tests, and builds documentation previews. Blocks merges if checks fail. | -| **Main** (`main.yml`) | Push to `main` | Post-merge validation. Runs unit/integration/e2e tests, quality/security checks, and updates the `latest` documentation. | -| **Nightly** (`nightly.yml`) | Cron (Daily 00:00 UTC) | Runs extended regression tests, alpha builds, nightly documentation deployment, and deeper security analysis. | -| **Release** (`release.yml`) | Push of `v*.*.*` tag | Performs full verification, builds immutable release packages, versioned documentation, and publishes artifacts. | -| **Weekly** (`weekly.yml`) | Cron (Sun 00:00 UTC) | Conducts dependency hygiene and full test suite regression to catch configuration drift. | +## CI/CD Workflows -### PR Feedback & Cleanup +We maintain high quality gates using git workflows, automated reviews, and GitHub Actions pipelines. -- **Safe PR Commenting:** The `pr_comment.yml` workflow uses `workflow_run` to securely post CI status comments on pull requests, circumventing write permission limits on forks. -- **Environment Cleanup:** When a PR is closed or merged, `development_cleanup.yml` automatically removes ephemeral documentation environments and artifacts to maintain a clean workspace. +### Version Control Standards -### Reusable Templates +- **Tools**: Git +- **Workflow & Commands**: + - **Branching Model**: Standard branch prefixes are enforced: + - Features: `feature/short-description` + - Bugs: `bugfix/short-description` + - Docs: `docs/short-description` + - **Commit Messages**: Enforce [Conventional Commits](https://www.conventionalcommits.org/) (e.g. `feat: ...`, `fix: ...`, `docs: ...`). + - **Versioning Tags**: Release tags must follow semver format (`v*.*.*`). -Our pipelines rely on modular templates located in `.github/workflows/_*.yml` (e.g., `_tests.yml`, `_quality.yml`, `_docs.yml`, `_security.yml`, `_build_package.yml`). This ensures testing granularity and linting rules remain perfectly consistent across all stages of the software lifecycle. +### Repository Policy & Pull Requests -## Building Documentation +- **Tools**: GitHub Pull Requests and Review tools +- **Workflow & Commands**: + - Open a PR against the `main` branch. + - All pipeline checks must pass (Linting, static typing, security gates, unit/integration/e2e tests). + - Require review and approval from at least one core maintainer before merging. -Our documentation is built using [MkDocs Material](https://squidfunk.github.io/mkdocs-material/). To preview documentation changes locally: +### GitHub Actions Architecture -```bash -# Hatch manages the isolated docs environment -# Serve documentation on http://127.0.0.1:8000 with hot-reload -hatch run docs:serve -``` +Our pipelines use a modular approach to avoid duplication: + +- **Tools**: GitHub Actions +- **Configuration / Manifest Files**: Reusable actions under `.github/actions/...` and triggers under `.github/workflows/...` +- **Workflow & Commands**: + - **Reusable Actions (`.github/actions/...`)**: Common build, cache, and test configuration tasks. + - **Workflows (`.github/workflows/...`)**: Triggered pipelines separated into: + - **Core Pipelines**: + - `development.yml`: PR tests, checks, and docs preview. + - `main.yml`: Merges to main; updates latest docs. + - `nightly.yml`: Deep vulnerabilities and regression test schedules. + - `release.yml`: Tag pushes (`v*.*.*`); packages binary builds. + - `weekly.yml`: Dependency and environment health verification. + - **Utility Workflows**: + - `development_cleanup.yml`: Cleans up transient PR doc deployments. -For further assistance, please refer to our [SUPPORT.md](SUPPORT.md). +### Security & Code Scanning Gates +- **Tools**: [Semgrep](https://semgrep.dev/), [Dependabot](https://github.com/dependabot), [Trufflehog](https://github.com/trufflesecurity/trufflehog), and [Trivy](https://github.com/aquasecurity/trivy) +- **Workflow & Commands**: + - Automatic background audits for repository code issues, secret leaks, and package vulnerabilities during PR checks. + +______________________________________________________________________ + +For additional assistance, please refer to our [SUPPORT.md](SUPPORT.md). ## File: CONTRIBUTING.md @@ -526,7 +632,6 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) By contributing to `rustarium`, you agree that your contributions will be licensed under its [Apache 2.0 License](LICENSE). - ## File: SECURITY.md # Security Policy for `rustarium` @@ -552,7 +657,7 @@ Please check the table below for the versions of `rustarium` that are currently If you discover a security vulnerability, please bring it to our attention right away using one of the following methods: 1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/markurtz/rustarium/security/advisories)** of this repository. -1. **Email:** Send your report directly to **[INSERT EMAIL ADDRESS OR SECURITY CONTACT]**. *(Optional: Encrypt your email using our PGP key: [INSERT PGP KEY LINK/FINGERPRINT])* +1. **Email:** Send your report directly to **contact the maintainers**. ### What to Include in Your Report @@ -588,7 +693,6 @@ We will handle your report with strict confidentiality. Our process is as follow *(Note: We currently do not operate a bug bounty program. Disclosures are greatly appreciated but are not eligible for financial rewards at this time.)* - ## File: SUPPORT.md # Support for `rustarium` @@ -614,16 +718,14 @@ Before reaching out, we recommend checking the following resources. Many common If you cannot find an answer in the documentation or existing issues, please open a new issue. To help us resolve your issue faster, please choose the correct venue: -| Issue Type | Venue | Description | -| :--------------------- | :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | +| Issue Type | Venue | Description | +| :--------------------- | :-------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | **Bug Report** | [GitHub Issues](https://github.com/markurtz/rustarium/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | | **Feature Request** | [GitHub Issues](https://github.com/markurtz/rustarium/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | -| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/rustarium/discussions/new) | Start a discussion for questions about how to use `rustarium`, architecture queries, or advice. | +| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/rustarium/discussions/new) | Start a discussion for questions about how to use `rustarium`, architecture queries, or advice. | Feel free to join the conversation on GitHub Discussions to connect with other users and maintainers. ## Commercial Support - - At this time, there is no official commercial support available for `rustarium`. Support is provided on a best-effort basis by the open-source community and maintainers. diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index a5b3d47..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,141 +0,0 @@ ---- -site_name: "rustarium" -site_description: "A high-performance, telemetrized process orchestrator and sandbox for Python\ - \ and WASM, forged in Rust." -site_author: "markurtz" -site_url: "https://markurtz.github.io/rustarium/" -repo_url: "https://github.com/markurtz/rustarium" -repo_name: "markurtz/rustarium" -edit_uri: edit/main/docs/ -extra: - version: - provider: mike - project_name: "rustarium" - project_description: "A high-performance, telemetrized process orchestrator and sandbox\ - \ for Python and WASM, forged in Rust." - org_name: "markurtz" -# org_email: "" - docs_url: "https://markurtz.github.io/rustarium/" - min_python: "3.9" - current_major_version: "0" -# slack_url: "https://slack.example.com" -# blog_url: "https://blog.example.com" - roadmap_url: "https://github.com/markurtz/rustarium/milestones" -theme: - name: material - logo: assets/branding/icon-white.svg - favicon: assets/branding/icon-black.svg - features: - - navigation.tabs - - navigation.tabs.sticky - - navigation.sections - - navigation.expand - - navigation.top - - navigation.indexes - - search.suggest - - search.highlight - - search.share - - content.code.copy - - content.code.annotate - - content.code.select - - content.action.edit - - content.tabs.link - - content.tooltips - - toc.follow - - toc.integrate - - header.autohide - palette: - # Palette toggle for light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: indigo - accent: indigo - toggle: - icon: material/brightness-7 - name: Switch to dark mode - # Palette toggle for dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: indigo - accent: indigo - toggle: - icon: material/brightness-4 - name: Switch to light mode -markdown_extensions: - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - - pymdownx.highlight: - anchor_linenums: true - line_spans: __span - pygments_lang_class: true - - pymdownx.inlinehilite - - pymdownx.snippets: - base_path: - - "." - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - - admonition - - pymdownx.details - - pymdownx.tabbed: - alternate_style: true - - attr_list - - md_in_html - - tables - - footnotes - - def_list - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.magiclink - - pymdownx.smartsymbols - - pymdownx.caret - - pymdownx.mark - - pymdownx.tilde - - toc: - permalink: true -plugins: - - gen-files: - scripts: - - docs/scripts/gen_ref_pages.py - - literate-nav: - nav_file: SUMMARY.md - - macros - - mike - - minify: - minify_html: true - - mkdocs-nav-weight - - mkdocstrings: - handlers: - python: - paths: - - src - options: - docstring_style: sphinx - - search - - section-index - - tags -extra_css: - - stylesheets/extra.css -extra_javascript: - - scripts/extra.js -# --------------------------------------------------------------------------- -# LLM & AI Tooling -# --------------------------------------------------------------------------- -# llms.txt (root-level file) is served at /llms.txt on the deployed docs site. -# It follows the llmstxt.org specification and provides a curated, machine- -# readable index of documentation for LLM consumers and AI coding agents. -# A companion file, llms-full.txt, contains the concatenated documentation. -# The root AGENTS.md contains agent-specific coding instructions; it is also -# surfaced in the docs nav under Community > AI Agent Guide. -# --------------------------------------------------------------------------- -validation: - nav: - omitted_files: warn - not_found: warn - absolute_links: warn - links: - absolute_links: warn - unrecognized_links: warn diff --git a/pyproject.toml b/pyproject.toml index 9478880..758df7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,71 +1,75 @@ [build-system] -requires = ["hatchling", "gitversioned[hatchling]"] -build-backend = "hatchling.build" +requires = ["maturin>=1.5,<2.0"] +build-backend = "maturin" [project] name = "rustarium" dynamic = ["version"] -description = "A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust." +description = "A high-performance, telemetrized process orchestrator and sandbox for Python, forged in Rust." readme = "README.md" requires-python = ">=3.10" license = { text = "Apache-2.0" } -authors = [{ name = "markurtz" }] +authors = [{ name = "Mark Kurtz" }] classifiers = [ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ - "click>=8.0.0", - "loguru>=0.7.0", - "pydantic>=2.0.0", - "pydantic-settings>=2.0.0", + "loguru>=0.7.0", + "pydantic>=2.0.0", + "pydantic-settings>=2.0.0", + "typer>=0.12.0", ] [project.optional-dependencies] -opentelemetry = [ - "opentelemetry-api>=1.0.0", - "opentelemetry-sdk>=1.0.0", -] +opentelemetry = ["opentelemetry-api>=1.0.0", "opentelemetry-sdk>=1.0.0"] [dependency-groups] -dev = [ - # testing - "pytest~=9.0", - "pytest-asyncio~=1.3", - "pytest-cov~=7.1", - "pytest-mock~=3.15", - "respx~=0.23", - - # code quality - "mypy~=2.1", - "pre-commit~=4.1", - "ruff~=0.15", - - # docs quality - "mdformat~=1.0", - "mdformat-frontmatter~=2.0", - "mdformat-gfm~=1.0", - - # general and configurations - "yamlfix~=1.19", +test = [ + "pytest>=9.0,<10", + "pytest-asyncio>=1.3,<2", + "pytest-cov>=7.1,<8", + "pytest-mock>=3.15,<4", + "respx>=0.23,<1", + "pytest-xdist>=3.5,<4", +] +lint = [ + "ty", + "ruff>=0.15,<1", + "mdformat>=1.0,<2", + "mdformat-frontmatter>=2.0,<3", + "mdformat-gfm>=1.0,<2", + "mdformat-footnote>=0.1.1,<1", + "mdformat-gfm-alerts>=1.0.1,<3", + "yamllint>=1.35,<2", + "urlchecker>=0.0.35", + "phmdoctest>=1.4,<2", + "yamlfix>=1.19,<2", + "taplo", ] docs = [ - # documentation - "mike~=2.2", - "mkdocs~=1.6", - "mkdocstrings[python]~=1.0", - "mkdocs-gen-files~=0.5", - "mkdocs-literate-nav~=0.6", - "mkdocs-macros-plugin~=1.5", - "mkdocs-material~=9.7", - "mkdocs-minify-plugin~=0.8", - "mkdocs-nav-weight~=0.3", - "mkdocs-section-index~=0.3", - "pymdown-extensions~=10.21", + "zensical>=0.0.40", + "mkdocs-gen-files>=0.5.0", + "mkdocstrings>=0.24.0", + "mkdocstrings-python>=1.9.0", +] +security = [ + "detect-secrets>=1.5,<2", + "checkov>=3.0,<4", + "semgrep>=1.73,<2", + "pip-audit>=2.7,<3", +] +environment = ["hatch>=1.12.0"] +dev = [ + { include-group = "test" }, + { include-group = "lint" }, + { include-group = "docs" }, + { include-group = "security" }, + { include-group = "environment" }, ] [project.scripts] @@ -77,8 +81,12 @@ Repository = "https://github.com/markurtz/rustarium.git" Issues = "https://github.com/markurtz/rustarium/issues" Documentation = "https://markurtz.github.io/rustarium/" -[tool.hatch.version] -source = "gitversioned" +[tool.maturin] +features = ["pyo3/extension-module"] +manifest-path = "crates/rustarium-core/Cargo.toml" +module-name = "rustarium._rust" +python-source = "src" +include = ["src/rustarium/version.py"] [tool.gitversioned] source_type = ["tag"] @@ -94,106 +102,338 @@ dev = "patch" [tool.hatch.envs.default] dependency-groups = ["dev"] -# ------------------------------------------------------------------------------ -# Quality & Style: Ruff, mdformat, yamlfix -# ------------------------------------------------------------------------------ -[tool.hatch.envs.lint] -detached = true -dependency-groups = ["dev"] +[tool.hatch.envs.default.env-vars] +PYTHON_TARGETS = "docs examples scripts src tests" +RUST_MANIFEST = "crates/rustarium-core/Cargo.toml" +OCI_IMAGE = "rustarium:latest" +MDFORMAT_TARGETS = ".devcontainer/ .github/ crates/ docs/*.md docs/community/ docs/examples/ docs/getting-started/ docs/guides/ examples/ scripts/ src/ tests/ *.md" +YAML_TARGETS = ".devcontainer/ .github/ crates/ docs/ examples/ scripts/ src/ tests/ *.yml *.yaml" +TOML_TARGETS = ".devcontainer/ .github/ crates/ docs/ examples/ scripts/ src/ tests/ *.toml" +PYTHON_SRC = "src" +PYTHON_UNIT_TESTS = "tests/python/unit" +PYTHON_INT_TESTS = "tests/python/integration" +PYTHON_COV_DIR = "coverage/python" +RUST_COV_DIR = "coverage/rust" +E2E_TESTS = "tests/e2e" +DOC_TESTS_PATH = ".tests/docs" +SECRETS_BASELINE = ".detect-secrets.scan.json" +RUN_OCI_SCRIPT = "scripts/run_oci.py" +CHECK_LINKS_SCRIPT = "scripts/check_links.py" +GENERATE_DOC_TESTS_SCRIPT = "scripts/generate_doc_tests.py" + +[tool.hatch.envs.all] +template = "default" -[tool.hatch.envs.lint.scripts] -# Quality checks (Read-only) -check = [ - "ruff check {args:docs examples scripts src tests .github}", - "ruff format --check {args:docs examples scripts src tests .github}", - "mdformat --check {args:docs examples scripts src tests .github *.md}", - "yamlfix --check {args:docs examples scripts src tests .github *.yml .*.yaml}", +[tool.hatch.envs.all.scripts] +lint = [ + "hatch run python:lint {args}", + "hatch run rust:lint {args}", + "hatch run oci:lint {args}", + "hatch run project:lint {args}", ] -pre-commit = "SKIP=type-check pre-commit run --all-files --show-diff-on-failure" -# Style runs (Applies fixes) format = [ - "ruff check --fix {args:docs examples scripts src tests .github}", - "ruff format {args:docs examples scripts src tests .github}", - "mdformat {args:docs examples scripts src tests .github *.md}", - "yamlfix {args:docs examples scripts src tests .github *.yml .*.yaml}", + "hatch run python:format {args}", + "hatch run rust:format {args}", + "hatch run oci:format {args}", + "hatch run project:format {args}", +] +types = [ + "hatch run python:types {args}", + "hatch run rust:types {args}", + "hatch run oci:types {args}", + "hatch run project:types {args}", +] +security = [ + "hatch run python:security {args}", + "hatch run rust:security {args}", + "hatch run oci:security {args}", + "hatch run project:security {args}", +] +quality = [ + "hatch run all:format {args}", + "hatch run all:lint {args}", + "hatch run all:types {args}", + "hatch run all:security {args}", +] +build = [ + "hatch build {args}", + "hatch run oci:build {args}", + "hatch run project:docs {args}", +] +tests = ["hatch run tests-func {args}", "hatch run tests-e2e {args}"] +tests-cov = [ + "hatch run tests-func-cov {args}", + "hatch run tests-e2e-cov {args}", +] +tests-func = [ + "hatch run python:tests-func {args}", + "hatch run rust:tests-func {args}", + "hatch run oci:tests-func {args}", + "hatch run project:tests-func {args}", +] +tests-func-cov = [ + "hatch run python:tests-func-cov {args}", + "hatch run rust:tests-func-cov {args}", + "hatch run oci:tests-func-cov {args}", + "hatch run project:tests-func-cov {args}", +] +tests-unit = [ + "hatch run python:tests-unit {args}", + "hatch run rust:tests-unit {args}", + "hatch run oci:tests-unit {args}", + "hatch run project:tests-unit {args}", +] +tests-unit-cov = [ + "hatch run python:tests-unit-cov {args}", + "hatch run rust:tests-unit-cov {args}", + "hatch run oci:tests-unit-cov {args}", + "hatch run project:tests-unit-cov {args}", +] +tests-int = [ + "hatch run python:tests-int {args}", + "hatch run rust:tests-int {args}", + "hatch run oci:tests-int {args}", + "hatch run project:tests-int {args}", +] +tests-int-cov = [ + "hatch run python:tests-int-cov {args}", + "hatch run rust:tests-int-cov {args}", + "hatch run oci:tests-int-cov {args}", + "hatch run project:tests-int-cov {args}", +] +tests-e2e = [ + "hatch run python:tests-e2e {args}", + "hatch run rust:tests-e2e {args}", + "hatch run oci:tests-e2e {args}", + "hatch run project:tests-e2e {args}", ] +tests-e2e-cov = [ + "hatch run python:tests-e2e-cov {args}", + "hatch run rust:tests-e2e-cov {args}", + "hatch run oci:tests-e2e-cov {args}", + "hatch run project:tests-e2e-cov {args}", +] +docs = [ + "hatch run python:docs {args}", + "hatch run rust:docs {args}", + "hatch run oci:docs {args}", + "hatch run project:docs {args}", +] +docs-serve = ["hatch run docs {args}", "hatch run project:docs-serve {args}"] -# ------------------------------------------------------------------------------ -# Type Checking: Mypy -# ------------------------------------------------------------------------------ -[tool.hatch.envs.types] -# Needs project dependencies to resolve imports +# ============================================================================== +# Python Environment +# ============================================================================== +[tool.hatch.envs.python] template = "default" -[tool.hatch.envs.types.scripts] -check = "mypy --install-types --non-interactive {args:examples scripts src tests}" +[tool.hatch.envs.python.scripts] +lint = [ + "ruff check {args:{env:PYTHON_TARGETS}}", + "ruff format --check {args:{env:PYTHON_TARGETS}}", +] +format = [ + "ruff check --fix {args:{env:PYTHON_TARGETS}}", + "ruff format {args:{env:PYTHON_TARGETS}}", +] +types = "ty check {args:{env:PYTHON_TARGETS}}" +security = [ + "semgrep scan --error {args}", + "pip-audit {args}", + "ruff check --select S {args:{env:PYTHON_TARGETS}}", +] +tests-func = "pytest {args:{env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS}}" +tests-func-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "pytest --cov={env:PYTHON_SRC} --cov-context=test --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-func.md {args:{env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS}}", + "coverage report --contexts=\".*tests/python/unit/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-unit.md", + "coverage report --contexts=\".*tests/python/integration/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-int.md", + "python -c \"import pathlib; [p.write_text('\\n'.join(line for line in p.read_text().splitlines() if not line.startswith('Combined'))) for p in (pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-unit.md'), pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-int.md')) if p.exists()]\"", +] +tests-unit = "pytest {args:{env:PYTHON_UNIT_TESTS}}" +tests-unit-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-unit.md {args:{env:PYTHON_UNIT_TESTS}}", +] +tests-int = "pytest {args:{env:PYTHON_INT_TESTS}}" +tests-int-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-int.md {args:{env:PYTHON_INT_TESTS}}", +] +tests-e2e = [ + "hatch build", + "pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium", + "pytest {args:{env:E2E_TESTS}}", +] +tests-e2e-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "hatch build", + "pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium", + "pytest --cov=rustarium --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-e2e.md {args:{env:E2E_TESTS}}", +] +docs = [ + "python -c \"import pathlib; pathlib.Path('.docs').mkdir(exist_ok=True)\"", + "typer src/rustarium/__main__.py utils docs --name rustarium --output .docs/cli.md", +] -# ------------------------------------------------------------------------------ -# Testing: Pytest with segmented coverage -# ------------------------------------------------------------------------------ -[tool.hatch.envs.test] -# Use the default env which contains pytest and pytest-cov +# ============================================================================== +# Rust Environment +# ============================================================================== +[tool.hatch.envs.rust] template = "default" +post-install-commands = ["cargo install --locked cargo-run-bin"] -[tool.hatch.envs.test.scripts] -# Standard runs (no coverage) -all = "pytest tests {args}" -unit = "pytest tests/unit {args}" -integration = "pytest tests/integration {args}" -e2e = "pytest tests/e2e {args}" - -# Coverage specific runs -# These use --cov=src to ensure only your source code is measured -unit-cov = "pytest --cov=src --cov-report=term-missing --cov-report=html:docs/coverage/unit/htmlcov tests/unit {args}" -integration-cov = "pytest --cov=src --cov-report=term-missing --cov-report=html:docs/coverage/integration/htmlcov tests/integration {args}" -e2e-cov = "pytest --cov=src --cov-report=term-missing --cov-report=html:docs/coverage/e2e/htmlcov tests/e2e {args}" -# Full suite coverage -all-cov = "pytest --cov=src --cov-report=term-missing --cov-report=html:docs/coverage/all/htmlcov tests {args}" - -# ------------------------------------------------------------------------------ -# Documentation: MkDocs -# ------------------------------------------------------------------------------ -[tool.hatch.envs.docs] -detached = true -dependency-groups = ["docs"] - -[tool.hatch.envs.docs.env-vars] -DISABLE_MKDOCS_2_WARNING = "true" - -[tool.hatch.envs.docs.scripts] -build = "mkdocs build --clean --strict" -serve = "mkdocs serve --dev-addr 0.0.0.0:8000" - -# ------------------------------------------------------------------------------ -# Build Configuration (hatch build) -# ------------------------------------------------------------------------------ -[tool.hatch.build.targets.sdist] -exclude = [ - "/.github", - "/docs", - "/tests", +[tool.hatch.envs.rust.scripts] +lint = [ + "cargo fmt --manifest-path {env:RUST_MANIFEST} --all -- --check {args}", + "cargo clippy --manifest-path {env:RUST_MANIFEST} --all-targets -- -D warnings {args}", +] +format = [ + "cargo fmt --manifest-path {env:RUST_MANIFEST} --all {args}", + "cargo clippy --manifest-path {env:RUST_MANIFEST} --fix --allow-dirty --allow-staged {args}", +] +types = "cargo check --manifest-path {env:RUST_MANIFEST} --all-targets {args}" +security = ["cargo bin cargo-audit {args}", "cargo bin cargo-deny check {args}"] +install-tools = ["cargo install --locked cargo-run-bin"] +tests-func = "cargo test --manifest-path {env:RUST_MANIFEST} --all-targets {args}" +tests-func-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --all-targets --json --output-path {env:RUST_COV_DIR}/coverage_tests-func.json {args}", + "covgate check {env:RUST_COV_DIR}/coverage_tests-func.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-func.md", +] +tests-unit = "cargo test --manifest-path {env:RUST_MANIFEST} --lib --bins {args}" +tests-unit-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --lib --bins --json --output-path {env:RUST_COV_DIR}/coverage_tests-unit.json {args}", + "covgate check {env:RUST_COV_DIR}/coverage_tests-unit.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-unit.md", +] +tests-int = "cargo test --manifest-path {env:RUST_MANIFEST} --test '*' {args}" +tests-int-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --test '*' --json --output-path {env:RUST_COV_DIR}/coverage_tests-int.json {args}", + "covgate check {env:RUST_COV_DIR}/coverage_tests-int.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-int.md", +] +tests-e2e = "echo '[INFO] No E2E tests are defined for the Rust environment'" +tests-e2e-cov = [ + "hatch run rust:tests-e2e {args}", + "echo '[INFO] No coverage analysis is applicable for E2E tests in the Rust environment'", +] +docs = [ + "cargo doc --manifest-path {env:RUST_MANIFEST} --target-dir .docs/rust_api --no-deps --workspace {args}", ] -[tool.hatch.build.targets.wheel] -packages = ["src/rustarium"] +# ============================================================================== +# OCI Environment +# ============================================================================== +[tool.hatch.envs.oci] +template = "default" +[tool.hatch.envs.oci.scripts] +lint = [ + "python {env:RUN_OCI_SCRIPT} hadolint {args}", + "python {env:RUN_OCI_SCRIPT} compose-config {args}", + "python {env:RUN_OCI_SCRIPT} dclint {args}", +] +format = "python {env:RUN_OCI_SCRIPT} dclint --fix {args}" +types = "echo '[INFO] Type checking is not applicable for the OCI environment' {args}" +security = [ + "python {env:RUN_OCI_SCRIPT} dockle {args}", + "python {env:RUN_OCI_SCRIPT} trivy {args}", +] +tests-func = [ + "hatch run oci:tests-unit {args}", + "hatch run oci:tests-int {args}", +] +tests-func-cov = [ + "hatch run oci:tests-unit-cov {args}", + "hatch run oci:tests-int-cov {args}", +] +tests-unit = "echo '[INFO] No unit tests are defined for the OCI environment' {args}" +tests-unit-cov = [ + "hatch run oci:tests-unit {args}", + "echo '[INFO] No coverage analysis is applicable for unit tests in the OCI environment'", +] +tests-int = "echo '[INFO] No integration tests are defined for the OCI environment' {args}" +tests-int-cov = [ + "hatch run oci:tests-int {args}", + "echo '[INFO] No coverage analysis is applicable for integration tests in the OCI environment'", +] +tests-e2e = ["hatch run oci:build", "python {env:RUN_OCI_SCRIPT} cstest {args}"] +tests-e2e-cov = [ + "hatch run oci:tests-e2e {args}", + "echo '[INFO] No coverage analysis is applicable for E2E tests in the OCI environment'", +] +docs = "echo '[INFO] No documentation is defined for the OCI environment' {args}" +build = "docker build -t {env:OCI_IMAGE} . {args}" -# --- Tool Configurations --- -[tool.mypy] -files = ["src/rustarium", "tests"] -python_version = "3.10" -warn_redundant_casts = true -warn_unused_ignores = false -show_error_codes = true -namespace_packages = true -exclude = ["venv", "build", "dist", ".venv", "examples/.*/setup\\.py"] -follow_imports = "normal" -ignore_missing_imports = true +# ============================================================================== +# Project Environment +# ============================================================================== +[tool.hatch.envs.project] +template = "default" + +[tool.hatch.envs.project.scripts] +lint = [ + "mdformat --check {args:{env:MDFORMAT_TARGETS}}", + "yamlfix --check {args:{env:YAML_TARGETS}}", + "yamllint {args:{env:YAML_TARGETS}}", + "taplo check {args:{env:TOML_TARGETS}}", +] +format = [ + "mdformat {args:{env:MDFORMAT_TARGETS}}", + "yamlfix {args:{env:YAML_TARGETS}}", + "taplo fmt {args:{env:TOML_TARGETS}}", +] +types = "echo '[INFO] Type checking is not applicable for the project environment'" +security = [ + "detect-secrets scan --baseline {env:SECRETS_BASELINE} {args:.}", + "python -m checkov.main --quiet {args:-d .}", +] +security-update = ["detect-secrets scan {args:.} > {env:SECRETS_BASELINE}"] +tests-func = ["hatch run project:tests-unit", "hatch run project:tests-int"] +tests-func-cov = [ + "hatch run project:tests-unit-cov", + "hatch run project:tests-int-cov", +] +tests-unit = "echo '[INFO] No unit tests are defined for the project environment'" +tests-unit-cov = [ + "hatch run project:tests-unit", + "echo '[INFO] No coverage analysis is applicable for unit tests in the project environment'", +] +tests-int = [ + "python {env:GENERATE_DOC_TESTS_SCRIPT} {args:{env:PYTHON_TARGETS}}", + "hatch -e python run pytest {env:DOC_TESTS_PATH} {args}", +] +tests-int-cov = [ + "hatch run project:tests-int", + "echo '[INFO] No coverage analysis is applicable for integration tests in the project environment'", +] +tests-e2e = "python {env:CHECK_LINKS_SCRIPT} {args:{env:MDFORMAT_TARGETS}}" +tests-e2e-cov = [ + "hatch run project:tests-e2e", + "echo '[INFO] No coverage analysis is applicable for E2E tests in the project environment'", +] +docs = [ + "python -m zensical {args:build}", + "python -c \"import shutil, pathlib; src = pathlib.Path('.docs/rust_api/doc'); dst = pathlib.Path('site/reference/rust_api'); dst.mkdir(parents=True, exist_ok=True); [shutil.copytree(p, dst / p.name, dirs_exist_ok=True) if p.is_dir() else shutil.copy2(p, dst / p.name) for p in src.glob('*')] if src.exists() else None\"", +] +docs-serve = ["python -m zensical serve"] + + +[tool.ty.src] +include = ["src/rustarium", "tests"] [tool.ruff] line-length = 88 indent-width = 4 -exclude = ["build", "dist", "env", ".venv", "src/rustarium/version.py"] +exclude = [ + "build", + "dist", + "env", + ".venv", + "src/rustarium/version.py", + ".tests", +] [tool.ruff.format] quote-style = "double" @@ -201,92 +441,102 @@ indent-style = "space" [tool.ruff.lint] ignore = [ - "COM812", # ignore trailing comma errors due to older Python versions - "ISC001", # implicit string concatenation (often disabled when using ruff format) - "PD011", # ignore .values usage since ruff assumes it's a Pandas DataFrame - "PLR0913", # ignore too many arguments in function definitions - "PLW1514", # allow Path.open without encoding - "RET505", # allow `else` blocks - "RET506", # allow `else` blocks - "S311", # allow standard pseudo-random generators - "TC001", # ignore imports used only for type checking - "TC002", # ignore imports used only for type checking - "TC003", # ignore imports used only for type checking + "COM812", # ignore trailing comma errors due to older Python versions + "ISC001", # implicit string concatenation (often disabled when using ruff format) + "PD011", # ignore .values usage since ruff assumes it's a Pandas DataFrame + "PLR0913", # ignore too many arguments in function definitions + "PLW1514", # allow Path.open without encoding + "RET505", # allow `else` blocks + "RET506", # allow `else` blocks + "S311", # allow standard pseudo-random generators + "TC001", # ignore imports used only for type checking + "TC002", # ignore imports used only for type checking + "TC003", # ignore imports used only for type checking ] select = [ - # Rules reference: https://docs.astral.sh/ruff/rules/ - - # Code Style / Formatting - "E", # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length - "W", # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length - "A", # flake8-builtins: prevents shadowing of Python built-in names - "C", # Convention: ensures code adheres to specific style and formatting conventions - "COM", # flake8-commas: enforces the correct use of trailing commas - "ERA", # eradicate: detects commented-out code that should be removed - "I", # isort: ensures imports are sorted in a consistent manner - "ICN", # flake8-import-conventions: enforces import conventions for better readability - "N", # pep8-naming: enforces PEP 8 naming conventions for classes, functions, and variables - "NPY", # NumPy: enforces best practices for using the NumPy library - "PD", # pandas-vet: enforces best practices for using the pandas library - "PT", # flake8-pytest-style: enforces best practices and style conventions for pytest tests - "PTH", # flake8-use-pathlib: encourages the use of pathlib over os.path for file system operations - "Q", # flake8-quotes: enforces consistent use of single or double quotes - "TCH", # flake8-type-checking: enforces type checking practices and standards - "TID", # flake8-tidy-imports: enforces tidy and well-organized imports - "RUF022", # flake8-ruff: enforce sorting of __all__ in modules - - # Code Structure / Complexity - "C4", # flake8-comprehensions: improves readability and performance of list, set, and dict comprehensions - "C90", # mccabe: checks for overly complex code using cyclomatic complexity - "ISC", # flake8-implicit-str-concat: prevents implicit string concatenation - "PIE", # flake8-pie: identifies and corrects common code inefficiencies and mistakes - "R", # Refactor: suggests improvements to code structure and readability - "SIM", # flake8-simplify: simplifies complex expressions and improves code readability - - # Code Security / Bug Prevention - "ARG", # flake8-unused-arguments: detects unused function and method arguments - "ASYNC", # flake8-async: identifies incorrect or inefficient usage patterns in asynchronous code - "B", # flake8-bugbear: detects common programming mistakes and potential bugs - "BLE", # flake8-blind-except: prevents blind exceptions that catch all exceptions without handling - "E", # Error: detects and reports errors in the code - "F", # Pyflakes: detects unused imports, shadowed imports, undefined variables, and various formatting errors in string operations - "INP", # flake8-no-pep420: prevents implicit namespace packages by requiring __init__.py - "PGH", # pygrep-hooks: detects deprecated and dangerous code patterns - "PL", # Pylint: comprehensive source code analyzer for enforcing coding standards and detecting errors - "RSE", # flake8-raise: ensures exceptions are raised correctly - "S", # flake8-bandit: detects security issues and vulnerabilities in the code - "SLF", # flake8-self: prevents incorrect usage of the self argument in class methods - "T10", # flake8-debugger: detects the presence of debugging tools such as pdb - "T20", # flake8-print: detects print statements left in the code - "UP", # pyupgrade: automatically upgrades syntax for newer versions of Python - "W", # Warning: provides warnings about potential issues in the code - "YTT", # flake8-2020: identifies code that will break with future Python releases - - # Code Documentation - "FIX", # flake8-fixme: detects FIXMEs and other temporary comments that should be resolved + # Rules reference: https://docs.astral.sh/ruff/rules/ + + # Code Style / Formatting + "E", # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length + "W", # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length + "A", # flake8-builtins: prevents shadowing of Python built-in names + "C", # Convention: ensures code adheres to specific style and formatting conventions + "COM", # flake8-commas: enforces the correct use of trailing commas + "ERA", # eradicate: detects commented-out code that should be removed + "I", # isort: ensures imports are sorted in a consistent manner + "ICN", # flake8-import-conventions: enforces import conventions for better readability + "N", # pep8-naming: enforces PEP 8 naming conventions for classes, functions, and variables + "NPY", # NumPy: enforces best practices for using the NumPy library + "PD", # pandas-vet: enforces best practices for using the pandas library + "PT", # flake8-pytest-style: enforces best practices and style conventions for pytest tests + "PTH", # flake8-use-pathlib: encourages the use of pathlib over os.path for file system operations + "Q", # flake8-quotes: enforces consistent use of single or double quotes + "TCH", # flake8-type-checking: enforces type checking practices and standards + "TID", # flake8-tidy-imports: enforces tidy and well-organized imports + "RUF022", # flake8-ruff: enforce sorting of __all__ in modules + + # Code Structure / Complexity + "C4", # flake8-comprehensions: improves readability and performance of list, set, and dict comprehensions + "C90", # mccabe: checks for overly complex code using cyclomatic complexity + "ISC", # flake8-implicit-str-concat: prevents implicit string concatenation + "PIE", # flake8-pie: identifies and corrects common code inefficiencies and mistakes + "R", # Refactor: suggests improvements to code structure and readability + "SIM", # flake8-simplify: simplifies complex expressions and improves code readability + + # Code Security / Bug Prevention + "ARG", # flake8-unused-arguments: detects unused function and method arguments + "ASYNC", # flake8-async: identifies incorrect or inefficient usage patterns in asynchronous code + "B", # flake8-bugbear: detects common programming mistakes and potential bugs + "BLE", # flake8-blind-except: prevents blind exceptions that catch all exceptions without handling + "F", # Pyflakes: detects unused imports, shadowed imports, undefined variables, and various formatting errors in string operations + "INP", # flake8-no-pep420: prevents implicit namespace packages by requiring __init__.py + "PGH", # pygrep-hooks: detects deprecated and dangerous code patterns + "PL", # Pylint: comprehensive source code analyzer for enforcing coding standards and detecting errors + "RSE", # flake8-raise: ensures exceptions are raised correctly + "S", # flake8-bandit: detects security issues and vulnerabilities in the code + "SLF", # flake8-self: prevents incorrect usage of the self argument in class methods + "T10", # flake8-debugger: detects the presence of debugging tools such as pdb + "T20", # flake8-print: detects print statements left in the code + "UP", # pyupgrade: automatically upgrades syntax for newer versions of Python + "YTT", # flake8-2020: identifies code that will break with future Python releases + + # Code Documentation + "FIX", # flake8-fixme: detects FIXMEs and other temporary comments that should be resolved ] [tool.ruff.lint.extend-per-file-ignores] "tests/**/*.py" = [ - "S101", # asserts allowed in tests - "ARG", # Unused function args allowed in tests - "PLR2004", # Magic value used in comparison - "TCH002", # No import only type checking in tests - "SLF001", # enable private member access in tests - "S105", # allow hardcoded passwords in tests - "S106", # allow hardcoded passwords in tests - "S311", # allow standard pseudo-random generators in tests - "PT011", # allow generic exceptions in tests - "N806", # allow uppercase variable names in tests - "PGH003", # allow general ignores in tests - "PLR0915", # allow complex statements in tests - "S603", # allow subprocess with untrusted input in tests - "S607", # allow partial executable paths in tests - "T201", # allow print in tests + "S101", # asserts allowed in tests + "ARG", # Unused function args allowed in tests + "PLR2004", # Magic value used in comparison + "TCH002", # No import only type checking in tests + "SLF001", # enable private member access in tests + "S105", # allow hardcoded passwords in tests + "S106", # allow hardcoded passwords in tests + "S311", # allow standard pseudo-random generators in tests + "PT011", # allow generic exceptions in tests + "N806", # allow uppercase variable names in tests + "PGH003", # allow general ignores in tests + "PLR0915", # allow complex statements in tests + "S603", # allow subprocess with untrusted input in tests + "S607", # allow partial executable paths in tests + "T201", # allow print in tests ] "examples/**/*.py" = [ - "T201", # allow print in examples - "INP001", # allow implicit namespace packages + "T201", # allow print in examples + "INP001", # allow implicit namespace packages +] +"scripts/**/*.py" = [ + "T201", # allow print in scripts + "INP001", # allow implicit namespace packages + "S603", # allow subprocess check with untrusted input + "S607", # allow starting a process with a partial executable path + "PLC0415", # allow local imports (e.g. dynamic imports in build_docs) + "BLE001", # allow catching blind Exception +] +"docs/**/*.py" = [ + "T201", # allow print in docs + "INP001", # allow implicit namespace packages ] [tool.ruff.lint.isort] @@ -296,9 +546,9 @@ known-first-party = ["rustarium", "tests"] addopts = "-s -vvv --cache-clear" asyncio_mode = "auto" markers = [ - "smoke: quick tests to check basic functionality", - "sanity: detailed tests to ensure major functions work correctly", - "regression: tests to ensure that new changes do not break existing functionality" + "smoke: quick tests to check basic functionality", + "sanity: detailed tests to ensure major functions work correctly", + "regression: tests to ensure that new changes do not break existing functionality", ] testpaths = ["tests"] @@ -306,3 +556,6 @@ testpaths = ["tests"] line_length = 88 sequence_style = "block_style" preserve_quotes = true + +[tool.coverage.run] +patch = ["subprocess"] diff --git a/scripts/check_links.py b/scripts/check_links.py new file mode 100644 index 0000000..80adbe3 --- /dev/null +++ b/scripts/check_links.py @@ -0,0 +1,160 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Platform-agnostic utility to scan and check URLs in markdown files. + +This module provides a command-line interface powered by Typer and uses `urlchecker` +to scan files and directories, extracting and validating links for formatting and +availability. It integrates with environment variables to resolve target paths. +""" + +from __future__ import annotations + +import subprocess +from os import environ +from pathlib import Path +from typing import Annotated + +import typer +from loguru import logger + +__all__: Annotated[ + list[str], + "The list of public symbols exported by this module.", +] = ["app", "collect_markdown_files", "main"] + +app: Annotated[ + typer.Typer, + "The Typer application instance used to define and execute the link-checking CLI.", +] = typer.Typer(add_completion=False) + + +@app.command( + context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def main( + context: typer.Context, + targets: Annotated[ + list[str] | None, + typer.Argument(help="Files or directories to scan"), + ] = None, +) -> None: + """ + Scan targets and run urlchecker on markdown files. + + This command collects markdown files under the specified targets, environment + variables, or default directories, and invokes ``urlchecker`` on each to validate + all contained URLs. + + Example: + .. code-block:: bash + + hatch run project:tests-e2e docs/ README.md + + :param context: The Typer context containing parsed arguments and extra CLI options. + :param targets: An optional list of file or directory paths to check. If omitted, + targets are retrieved from the ``PROJECT_TARGETS`` or + ``MDFORMAT_TARGETS`` environment variables, falling back to a + default set of folders. + :return: None + :raises typer.Exit: If one or more link checks fail (exit code 1). + """ + resolved_targets = targets + if not resolved_targets: + env_targets = environ.get("PROJECT_TARGETS") or environ.get("MDFORMAT_TARGETS") + if env_targets: + resolved_targets = [ + target for target in env_targets.split() if "*" not in target + ] + else: + resolved_targets = [ + ".devcontainer", + ".github", + "crates", + "docs", + "examples", + "scripts", + "src", + "tests", + ] + + markdown_files = collect_markdown_files(resolved_targets) + extra_options = context.args + + failed = False + for md_file in markdown_files: + logger.info(f"Checking links in: {md_file}") + + # Run urlchecker + try: + subprocess.run( + [ + "urlchecker", + "check", + str(md_file), + "--file-types", + ".md", + "--exclude-patterns", + "localhost", + ] + + extra_options, + check=True, + ) + except (subprocess.CalledProcessError, FileNotFoundError) as error: + logger.error(f"Error checking {md_file}: {error}") + failed = True + + if failed: + raise typer.Exit(code=1) + + +def collect_markdown_files(targets: list[str]) -> list[Path]: + """ + Collect all target markdown files from the specified targets. + + This function searches the root directory and the provided targets (files or + directories) to assemble a sorted, unique list of markdown files for checking. + + Example: + .. code-block:: python + + from pathlib import Path + files = collect_markdown_files(["docs", "README.md"]) + + :param targets: A list of paths (directories or files) to scan for markdown files. + :return: A sorted list of Paths to markdown files. + """ + markdown_files: list[Path] = [] + + # Check root level md files + for path in Path().glob("*.md"): + markdown_files.append(path) + + # Check targeted subdirectories + for target in targets: + target_path = Path(target) + if target_path.exists(): + if target_path.is_file() and target_path.suffix == ".md": + markdown_files.append(target_path) + elif target_path.is_dir(): + markdown_files.extend(target_path.rglob("*.md")) + + # Sort files to ensure deterministic execution + markdown_files.sort() + return markdown_files + + +if __name__ == "__main__": + app() diff --git a/scripts/generate_doc_tests.py b/scripts/generate_doc_tests.py new file mode 100644 index 0000000..15e82f5 --- /dev/null +++ b/scripts/generate_doc_tests.py @@ -0,0 +1,170 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Extract and generate test files from markdown documents. + +This module uses ``phmdoctest`` to parse code blocks in markdown files and convert +them into executable pytest files. It helps automate code snippet verification in docs. +""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from os import environ +from pathlib import Path +from typing import Annotated + +import typer +from loguru import logger + +__all__ = ["main"] + +# Output directory for the generated test files. +_OUT_DIR = Path(".tests/docs") + +app: Annotated[ + typer.Typer, + "Typer CLI application instance for document test generation.", +] = typer.Typer( + help=( + "Platform-agnostic script to extract and generate test files from " + "markdown documents using phmdoctest." + ), + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) + + +# Resolve targets from arguments, environment variables, or defaults. +def _resolve_targets(targets_and_options: list[str]) -> list[str]: + targets = [arg for arg in targets_and_options if not arg.startswith("-")] + if targets: + return targets + + env_targets = environ.get("PROJECT_TARGETS") or environ.get("PYTHON_TARGETS") + if env_targets: + return [target for target in env_targets.split() if "*" not in target] + + return [ + ".devcontainer", + ".github", + "crates", + "docs", + "examples", + "scripts", + "src", + "tests", + ] + + +# Find all markdown files under targets, excluding reference and test directories. +def _find_markdown_files(targets: list[str]) -> list[Path]: + markdown_files = list(Path().glob("*.md")) + + for target in targets: + target_path = Path(target) + if not target_path.exists(): + continue + if target_path.is_file() and target_path.suffix == ".md": + markdown_files.append(target_path) + elif target_path.is_dir(): + markdown_files.extend(target_path.rglob("*.md")) + + return sorted( + path + for path in markdown_files + if ".tests" not in path.parts + and not ("docs" in path.parts and "reference" in path.parts) + ) + + +@app.callback(invoke_without_command=True) +def run_generate_doc_tests( + ctx: typer.Context, # noqa: ARG001 + targets_and_options: Annotated[ + list[str] | None, + typer.Argument( + help="Target paths and/or extra phmdoctest arguments.", + show_default=False, + ), + ] = None, +) -> None: + """ + Extract and generate test files from markdown documents. + + Examples: + >>> from typer.testing import CliRunner + >>> runner = CliRunner() + >>> result = runner.invoke(app, ["docs/"]) + + :param ctx: CLI execution context. + :param targets_and_options: Target paths or extra phmdoctest arguments. + :return: None. + """ + # Clean up and recreate .tests/docs directory + if _OUT_DIR.exists(): + shutil.rmtree(_OUT_DIR) + _OUT_DIR.mkdir(parents=True, exist_ok=True) + + targets_and_options = targets_and_options or [] + targets = _resolve_targets(targets_and_options) + markdown_files = _find_markdown_files(targets) + extra_options = [arg for arg in targets_and_options if arg.startswith("-")] + + failed = False + for markdown_file in markdown_files: + # Generate safe filename for python file: replace slashes and dots + # e.g., docs/getting-started/quickstart.md -> + # test_docs__getting-started__quickstart_md.py + safe_name = str(markdown_file).replace("/", "__").replace(".", "__") + out_file = _OUT_DIR / f"test_{safe_name}.py" + + logger.info("Generating tests from {} -> {}", markdown_file, out_file) + try: + subprocess.run( + [ + sys.executable, + "-m", + "phmdoctest", + str(markdown_file), + "--outfile", + str(out_file), + ] + + extra_options, + check=True, + ) + except (subprocess.CalledProcessError, FileNotFoundError) as error: + logger.error("Error generating tests for {}: {}", markdown_file, error) + failed = True + + if failed: + sys.exit(1) + + +def main() -> None: + """ + Execute the CLI application. + + Examples: + >>> main() + + :return: None. + """ + app() + + +if __name__ == "__main__": + main() diff --git a/scripts/run_oci.py b/scripts/run_oci.py new file mode 100644 index 0000000..f8b4722 --- /dev/null +++ b/scripts/run_oci.py @@ -0,0 +1,696 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +A unified platform-agnostic OCI runner script. + +This module provides CLI commands to run various OCI linting, testing, and auditing +tools (such as hadolint, dclint, dockle, trivy, and container-structure-test) +either locally or via fallback Docker containers. +""" + +from __future__ import annotations + +import contextlib +import shutil +import subprocess +import sys +from collections.abc import Generator +from os import environ +from pathlib import Path +from typing import Annotated + +import typer +from loguru import logger + +__all__ = [ + "IMAGE_NAME", + "SETTINGS_FILE_NAME", + "TAR_FILE", + "app", + "check_image_exists", + "cmd_compose_config", + "cmd_cstest", + "cmd_dclint", + "cmd_dockle", + "cmd_hadolint", + "cmd_trivy", + "is_docker_running", + "main", +] + +IMAGE_NAME: Annotated[ + str, + "The name of the OCI image to target for scanning, building, or auditing.", +] = environ.get("OCI_IMAGE", "rustarium:latest") + +SETTINGS_FILE_NAME: Annotated[ + str, + "The settings file name to accept/suppress from Dockle checks.", +] = "settings.py" + +TAR_FILE: Annotated[ + Path, + "The path to the temporary image tarball used by auditing tools.", +] = Path("rustarium.tar") + +app: Annotated[ + typer.Typer, + "The Typer CLI application instance for executing OCI tasks.", +] = typer.Typer( + help="Unified platform-agnostic OCI runner.", + no_args_is_help=True, + add_completion=False, +) + + +def is_docker_running() -> bool: + """ + Check if the Docker daemon is available and running. + + Example: + >>> is_docker_running() + True + + :return: True if the Docker daemon is responsive, False otherwise. + """ + try: + result = subprocess.run( + ["docker", "info"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + return result.returncode == 0 + except (subprocess.SubprocessError, FileNotFoundError): + return False + + +def check_image_exists(image: str) -> bool: + """ + Check if the specified Docker image exists locally. + + Example: + >>> check_image_exists("rustarium:latest") + True + + :param image: The name or tag of the Docker image. + :return: True if the image exists, False otherwise. + """ + try: + result = subprocess.run( + ["docker", "image", "inspect", image], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + return result.returncode == 0 + except subprocess.SubprocessError: + return False + + +def run_hadolint(extra_args: list[str]) -> None: + """ + Run Hadolint linter on Dockerfile. + + If Hadolint is installed locally, it runs it directly. Otherwise, it falls + back to running Hadolint within a Docker container. + + Example: + >>> run_hadolint(["--ignore", "DL3006"]) + + :param extra_args: Additional command line arguments passed to Hadolint. + :raises SystemExit: If the linter command returns a non-zero exit code. + """ + dockerfile = Path("Dockerfile") + if not dockerfile.exists(): + logger.warning(f"No {dockerfile} found. Skipping hadolint.") + return + + option_flags = { + "-c", + "--config", + "--ignore", + "--trusted-registry", + "-f", + "--format", + } + args = _add_default_positional(extra_args, str(dockerfile), option_flags) + + # Check if hadolint is installed locally + local_path = shutil.which("hadolint") + if local_path: + logger.info(f"Running local hadolint with args {args}...") + res = subprocess.run([local_path] + args, check=False) + if res.returncode != 0: + sys.exit(res.returncode) + return + + # Fallback to Docker + if not is_docker_running(): + logger.warning( + "hadolint is not installed locally and Docker is not running. " + f"Skipping hadolint with args {args}." + ) + return + + logger.info(f"Running hadolint via Docker with args {args}...") + try: + pwd = Path.cwd() + res = subprocess.run( + [ + "docker", + "run", + "--rm", + "-v", + f"{pwd}:/app", + "-w", + "/app", + "hadolint/hadolint", + "/bin/hadolint", + ] + + args, + check=False, + ) + if res.returncode != 0: + sys.exit(res.returncode) + except subprocess.SubprocessError as error: + logger.error(f"Failed to run hadolint via Docker: {error}") + sys.exit(1) + + +def run_dclint(extra_args: list[str]) -> None: + """ + Run dclint on compose files. + + If dclint is installed locally, it runs it directly. Otherwise, it falls + back to running dclint within a Docker container. + + Example: + >>> run_dclint(["-f", "docker-compose.yml"]) + + :param extra_args: Additional command line arguments passed to dclint. + :raises SystemExit: If the linter command returns a non-zero exit code. + """ + option_flags = { + "-f", + "--formatter", + "-c", + "--config", + "-o", + "--output-file", + "--max-warnings", + "-e", + "--exclude", + "--disable-rule", + } + args = _add_default_positional(extra_args, ".", option_flags) + + local_path = shutil.which("dclint") + if local_path: + logger.info(f"Running local dclint with args {args}...") + res = subprocess.run([local_path] + args, check=False) + if res.returncode != 0: + sys.exit(res.returncode) + return + + # Fallback to Docker + if not is_docker_running(): + logger.warning( + "dclint is not installed locally and Docker is not running. " + f"Skipping dclint with args {args}." + ) + return + + logger.info(f"Running dclint via Docker with args {args}...") + try: + pwd = Path.cwd() + res = subprocess.run( + [ + "docker", + "run", + "--rm", + "-v", + f"{pwd}:/app", + "-w", + "/app", + "zavoloklom/dclint", + ] + + args, + check=False, + ) + if res.returncode != 0: + sys.exit(res.returncode) + except subprocess.SubprocessError as error: + logger.error(f"Failed to run dclint via Docker: {error}") + sys.exit(1) + + +def run_compose_config(extra_args: list[str]) -> None: + """ + Validate docker compose configuration. + + Runs the `docker compose config` command to check compose files. + + Example: + >>> run_compose_config(["config", "-q"]) + + :param extra_args: Additional command line arguments passed to docker compose. + :raises SystemExit: If the validation command returns a non-zero exit code. + """ + if not is_docker_running(): + logger.warning( + "Docker is not running. Skipping docker compose config check " + f"with args {extra_args}." + ) + return + + logger.info(f"Checking docker compose config with args {extra_args}...") + args = extra_args if extra_args else ["config", "-q"] + res = subprocess.run(["docker", "compose"] + args, check=False) + if res.returncode != 0: + sys.exit(res.returncode) + + +def run_dockle(extra_args: list[str]) -> None: + """ + Run Dockle container audit. + + If Dockle is installed locally, it runs it directly. Otherwise, it falls + back to exporting the image to a tarball and running Dockle via Docker. + + Example: + >>> run_dockle([]) + + :param extra_args: Additional command line arguments passed to Dockle. + :raises SystemExit: If the audit command returns a non-zero exit code. + """ + local_path = shutil.which("dockle") + if local_path: + option_flags = { + "-c", + "--config", + "-f", + "--format", + "-o", + "--output", + "-i", + "--input", + "--accept-key", + "--ignore", + "--accept-file", + "-af", + } + args = extra_args.copy() + if not any(arg in args for arg in ["--accept-file", "-af"]): + args.extend(["--accept-file", SETTINGS_FILE_NAME]) + args = _add_default_positional(args, IMAGE_NAME, option_flags) + logger.info(f"Running local dockle with args {args}...") + res = subprocess.run([local_path] + args, check=False) + if res.returncode != 0: + sys.exit(res.returncode) + return + + # Fallback to Docker + if not is_docker_running(): + logger.warning( + "dockle is not installed locally and Docker is not running. " + f"Skipping dockle audit with args {extra_args}." + ) + return + + logger.info( + f"Running dockle via Docker against exported tarball with args {extra_args}..." + ) + try: + with _temp_image_tar(IMAGE_NAME, TAR_FILE): + pwd = Path.cwd() + args = extra_args.copy() + if not any(arg in args for arg in ["--input", "-i"]): + args.extend(["--input", str(TAR_FILE)]) + if not any(arg in args for arg in ["--accept-file", "-af"]): + args.extend(["--accept-file", SETTINGS_FILE_NAME]) + + res = subprocess.run( + [ + "docker", + "run", + "--rm", + "-v", + f"{pwd}:/app", + "-w", + "/app", + "goodwithtech/dockle:latest", + ] + + args, + check=False, + ) + if res.returncode != 0: + sys.exit(res.returncode) + except subprocess.SubprocessError as error: + logger.error(f"Failed to run dockle audit: {error}") + sys.exit(1) + + +def run_trivy(extra_args: list[str]) -> None: + """ + Run Trivy security scan. + + If Trivy is installed locally, it runs it directly. Otherwise, it falls + back to exporting the image to a tarball and running Trivy via Docker. + + Example: + >>> run_trivy(["--severity", "HIGH,CRITICAL"]) + + :param extra_args: Additional command line arguments passed to Trivy. + :raises SystemExit: If the security scan returns a non-zero exit code. + """ + local_path = shutil.which("trivy") + if local_path: + args = extra_args.copy() + if not any( + arg in args for arg in ["image", "fs", "repo", "config", "rootfs", "sbom"] + ): + args.insert(0, "image") + + if args[0] == "image": + option_flags = { + "-f", + "--format", + "-o", + "--output", + "-s", + "--severity", + "-c", + "--config", + "--vuln-type", + "--security-checks", + "--ignore-policy", + "--ignorefile", + "--cache-dir", + } + sub_args = _add_default_positional(args[1:], IMAGE_NAME, option_flags) + args = [args[0]] + sub_args + + logger.info(f"Running local trivy with args {args}...") + res = subprocess.run([local_path] + args, check=False) + if res.returncode != 0: + sys.exit(res.returncode) + return + + # Fallback to Docker + if not is_docker_running(): + logger.warning( + "trivy is not installed locally and Docker is not running. " + f"Skipping trivy scan with args {extra_args}." + ) + return + + logger.info( + f"Running trivy via Docker against exported tarball with args {extra_args}..." + ) + try: + with _temp_image_tar(IMAGE_NAME, TAR_FILE): + pwd = Path.cwd() + cache_dir = Path(pwd) / ".trivycache" + cache_dir.mkdir(exist_ok=True) + + args = extra_args.copy() + if not any( + arg in args + for arg in ["image", "fs", "repo", "config", "rootfs", "sbom"] + ): + args.insert(0, "image") + if not any(arg in args for arg in ["--input", "-i"]): + args.extend(["--input", str(TAR_FILE)]) + + res = subprocess.run( + [ + "docker", + "run", + "--rm", + "-v", + f"{pwd}:/app", + "-v", + f"{cache_dir}:/root/.cache", + "-w", + "/app", + "aquasec/trivy:latest", + ] + + args, + check=False, + ) + if res.returncode != 0: + sys.exit(res.returncode) + except subprocess.SubprocessError as error: + logger.error(f"Failed to run trivy scan: {error}") + sys.exit(1) + + +def run_cstest(extra_args: list[str]) -> None: + """ + Run Container Structure Test (cstest). + + If container-structure-test is installed locally, it runs it directly. + Otherwise, it runs container-structure-test via Docker with docker.sock mounted. + + Example: + >>> run_cstest([]) + + :param extra_args: Additional command line arguments passed to cstest. + :raises SystemExit: If the test command returns a non-zero exit code. + """ + local_path = shutil.which("container-structure-test") + if local_path: + logger.info(f"Running local container-structure-test with args {extra_args}...") + args = _build_cstest_args(extra_args, "cst.yaml") + res = subprocess.run([local_path] + args, check=False) + if res.returncode != 0: + sys.exit(res.returncode) + return + + # Fallback to Docker + if not is_docker_running(): + logger.warning( + "container-structure-test is not installed locally and " + f"Docker is not running. Skipping cstest with args {extra_args}." + ) + return + + if not check_image_exists(IMAGE_NAME): + logger.error( + f"Image {IMAGE_NAME} not found. Build it first (e.g. hatch run oci:build)." + ) + sys.exit(1) + + logger.info( + f"Running container-structure-test via Docker with args {extra_args}..." + ) + try: + pwd = Path.cwd() + args = _build_cstest_args(extra_args, "/etc/cstest/cst.yaml") + res = subprocess.run( + [ + "docker", + "run", + "--rm", + "-v", + f"{pwd}:/etc/cstest:ro", + "-v", + "/var/run/docker.sock:/var/run/docker.sock:ro", + "ghcr.io/googlecontainertools/container-structure-test:latest", + ] + + args, + check=False, + ) + if res.returncode != 0: + sys.exit(res.returncode) + except subprocess.SubprocessError as error: + logger.error(f"Failed to run container-structure-test: {error}") + sys.exit(1) + + +@app.command( + "hadolint", + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) +def cmd_hadolint(ctx: typer.Context) -> None: + """ + Run Hadolint linter on Dockerfile via the CLI. + + Example: + $ python run_oci.py hadolint --ignore DL3006 + + :param ctx: The Typer context containing extra command line arguments. + """ + run_hadolint(ctx.args) + + +@app.command( + "dclint", + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) +def cmd_dclint(ctx: typer.Context) -> None: + """ + Run dclint on compose files via the CLI. + + Example: + $ python run_oci.py dclint . + + :param ctx: The Typer context containing extra command line arguments. + """ + run_dclint(ctx.args) + + +@app.command( + "compose-config", + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) +def cmd_compose_config(ctx: typer.Context) -> None: + """ + Validate docker compose configuration via the CLI. + + Example: + $ python run_oci.py compose-config + + :param ctx: The Typer context containing extra command line arguments. + """ + run_compose_config(ctx.args) + + +@app.command( + "dockle", + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) +def cmd_dockle(ctx: typer.Context) -> None: + """ + Run Dockle container audit via the CLI. + + Example: + $ python run_oci.py dockle + + :param ctx: The Typer context containing extra command line arguments. + """ + run_dockle(ctx.args) + + +@app.command( + "trivy", + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) +def cmd_trivy(ctx: typer.Context) -> None: + """ + Run Trivy security scan via the CLI. + + Example: + $ python run_oci.py trivy --severity HIGH,CRITICAL + + :param ctx: The Typer context containing extra command line arguments. + """ + run_trivy(ctx.args) + + +@app.command( + "cstest", + context_settings={"ignore_unknown_options": True, "allow_extra_args": True}, +) +def cmd_cstest(ctx: typer.Context) -> None: + """ + Run Container Structure Test (cstest) via the CLI. + + Example: + $ python run_oci.py cstest --config cst.yaml + + :param ctx: The Typer context containing extra command line arguments. + """ + run_cstest(ctx.args) + + +@contextlib.contextmanager +def _temp_image_tar(image: str, tar_path: Path) -> Generator[None, None, None]: + """Save the Docker image to a tarball and ensure it is cleaned up.""" + if not check_image_exists(image): + logger.error( + f"Image {image} not found. Build it first (e.g. hatch run oci:build)." + ) + sys.exit(1) + + logger.info(f"Saving image {image} to {tar_path}...") + subprocess.run(["docker", "save", image, "-o", str(tar_path)], check=True) + try: + yield + finally: + if tar_path.exists(): + tar_path.unlink() + + +def _add_default_positional( + args: list[str], default_val: str, option_flags: set[str] +) -> list[str]: + """ + Append a default positional argument to the args list if none is present + and no help or version flags are requested. + + :param args: List of command line arguments. + :param default_val: The default positional argument to append. + :param option_flags: Set of option flags that take a parameter. + :return: The list of arguments, possibly with default_val appended. + """ + # Check for help/version flags first + if any(flag in args for flag in ["--help", "-h", "--version", "-v"]): + return args + + # Check for existing positional arguments + has_positional = False + skip_next = False + for argument in args: + if skip_next: + skip_next = False + continue + if argument.startswith("-"): + if argument in option_flags: + skip_next = True + else: + has_positional = True + break + + if not has_positional: + return args + [default_val] + return args + + +def _build_cstest_args(extra_args: list[str], default_config: str) -> list[str]: + """Build args for container-structure-test.""" + args = extra_args.copy() + if "test" not in args: + args.insert(0, "test") + if not any(arg in args for arg in ["--image", "-i"]): + args.extend(["--image", IMAGE_NAME]) + if not any(arg in args for arg in ["--config", "-c"]): + args.extend(["--config", default_config]) + return args + + +def main() -> None: + """ + Main execution entrypoint to run the Typer CLI application. + + Example: + >>> main() + """ + app() + + +if __name__ == "__main__": + main() diff --git a/src/rustarium/__init__.py b/src/rustarium/__init__.py index edc66e5..6af657b 100644 --- a/src/rustarium/__init__.py +++ b/src/rustarium/__init__.py @@ -8,8 +8,65 @@ from __future__ import annotations +from .client import AsyncRustariumClient, RustariumClient +from .exceptions import ( + ConfigurationError, + OrchestrationError, + RustariumError, + SecurityError, + WorkerError, +) from .logging import LoggingSettings, configure_logger, logger +from .schemas import ( + ConcurrentConfig, + ConstantRateConfig, + JobConfig, + JobContext, + Metrics, + PoissonConfig, + RustariumContext, + SecurityProfile, + UpdatePayload, + ViolationPolicy, + Workload, + WorkloadStatus, +) from .settings import Settings from .version import __version__ -__all__ = ["LoggingSettings", "Settings", "__version__", "configure_logger", "logger"] +try: + from ._rust import sum_as_string +except ImportError: + + def sum_as_string(a: int, b: int) -> str: + """Fallback sum function when compiled extension is not available.""" + return str(a + b) + + +__all__ = [ + "AsyncRustariumClient", + "ConcurrentConfig", + "ConfigurationError", + "ConstantRateConfig", + "JobConfig", + "JobContext", + "LoggingSettings", + "Metrics", + "OrchestrationError", + "PoissonConfig", + "RustariumClient", + "RustariumContext", + "RustariumError", + "SecurityError", + "SecurityProfile", + "Settings", + "UpdatePayload", + "ViolationPolicy", + "WorkerError", + "Workload", + "WorkloadStatus", + "__version__", + "configure_logger", + "logger", + "sum_as_string", +] diff --git a/src/rustarium/__main__.py b/src/rustarium/__main__.py index b4b4ae1..60fa19d 100644 --- a/src/rustarium/__main__.py +++ b/src/rustarium/__main__.py @@ -2,42 +2,51 @@ Main entrypoint for the rustarium package. This module provides the executable routine when the package is run directly -via the command line (e.g., ``python -m rustarium``). It initializes the logger -and settings, outputting the current version and configuration to verify -the installation and environment setup. +via the command line (e.g., ``python -m rustarium``). It uses Typer to define +the CLI application and commands. """ from __future__ import annotations -import click +from typing import Annotated -from rustarium import ( - LoggingSettings, - Settings, - __version__, - configure_logger, - logger, -) +import typer -__all__ = ["main"] +from rustarium.logging import LoggingSettings, configure_logger, logger +from rustarium.settings import Settings +from rustarium.version import __version__ +__all__ = ["main"] -@click.command(context_settings={"help_option_names": ["-h", "--help"]}) -@click.version_option(version=__version__, prog_name="rustarium") -def main() -> None: - """ - Execute the main routine. +app = typer.Typer( + help="Rustarium: High-performance process orchestrator and sandbox.", + context_settings={"help_option_names": ["-h", "--help"]}, +) - Initializes application settings and logging, demonstrating the basic startup - flow of the application. It logs the current version and the loaded configuration - settings. - Example: - .. code-block:: python +def _version_callback(value: bool) -> None: + """Callback to display the current version.""" + if value: + typer.echo(f"rustarium v{__version__}") + raise typer.Exit - from rustarium.__main__ import main - main() +@app.callback(invoke_without_command=True) +def _main_callback( + ctx: typer.Context, + version: Annotated[ # noqa: ARG001 + bool | None, + typer.Option( + "--version", + callback=_version_callback, + is_eager=True, + help="Show the application version and exit.", + ), + ] = None, +) -> None: + """ + Global setup for the CLI application. + Initializes application settings and logging. """ configure_logger( LoggingSettings( @@ -47,9 +56,43 @@ def main() -> None: filter=("rustarium", "__main__"), ) ) - logger.info("Hello from rustarium v{}!", __version__) settings = Settings() - logger.info("Settings: {}", settings) + + if ctx.invoked_subcommand is None: + logger.info("Hello from rustarium v{}!", __version__) + logger.info("Settings: {}", settings) + + +@app.command() +def diagnose() -> None: + """ + Diagnose the system environment requirements. + + Automated checks for OS support, cgroups v2 availability, Docker socket permissions, + and Python virtual environment state. + + TODO: Implement diagnostic checks. + """ + logger.info("Running Rustarium diagnostics...") + typer.echo("Diagnostic logic not yet implemented. (TODO)") + + +@app.command() +def setup() -> None: + """ + Guide the user through configuring the local environment. + + TODO: Implement setup wizard. + """ + logger.info("Running Rustarium setup...") + typer.echo("Setup logic not yet implemented. (TODO)") + + +def main() -> None: + """ + Execute the main routine via Typer. + """ + app() if __name__ == "__main__": diff --git a/src/rustarium/_rust.pyi b/src/rustarium/_rust.pyi new file mode 100644 index 0000000..b79556a --- /dev/null +++ b/src/rustarium/_rust.pyi @@ -0,0 +1,20 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Type stubs for the Rust binary extension module. +""" + +def sum_as_string(a: int, b: int) -> str: + """Sum two integers and return the result as a string.""" diff --git a/src/rustarium/client.py b/src/rustarium/client.py new file mode 100644 index 0000000..e96b578 --- /dev/null +++ b/src/rustarium/client.py @@ -0,0 +1,94 @@ +""" +Primary SDK clients for interacting with the Rustarium orchestration engine. +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator, Generator, Iterable +from typing import Annotated, Any, cast + +from pydantic_core import to_json + +from rustarium.schemas import JobConfig, UpdatePayload + +__all__ = [ + "AsyncRustariumClient", + "RustariumClient", +] + + +class AsyncRustariumClient: + """ + Asynchronous client for interacting with the Rustarium orchestration daemon. + """ + + def __init__(self) -> None: + """ + Initialize the asynchronous client. + + TODO: Implement PyO3 bridging and Rust daemon initialization. + """ + + async def submit( + self, + workloads: Annotated[ # noqa: ARG002 + Iterable[Any], + "The iterable of workloads or data payloads to process.", + ], + default_config: Annotated[ + JobConfig | None, + "Default configuration for the batch.", + ] = None, + ) -> AsyncGenerator[UpdatePayload, None]: + """ + Submit a batch of workloads to the orchestration engine. + + TODO: + - Serialize configuration using `pydantic_core.to_json()` + - Start the PyO3 async generator to drive the Rust/Tokio engine. + - Yield `UpdatePayload` instances parsed from the UDS stream. + """ + # Example to ensure pydantic_core usage is documented: + if default_config is not None: + _serialized_config = to_json(default_config, by_alias=True) # noqa: F841 + + # Stubbed yield to satisfy the generator signature + yield cast("UpdatePayload", NotImplemented) + + +class RustariumClient: + """ + Synchronous client for interacting with the Rustarium orchestration daemon. + """ + + def __init__(self) -> None: + """ + Initialize the synchronous client. + + TODO: Set up background worker thread insulation. + """ + self._async_client = AsyncRustariumClient() + + def submit( + self, + workloads: Annotated[ # noqa: ARG002 + Iterable[Any], + "The iterable of workloads or data payloads to process.", + ], + default_config: Annotated[ # noqa: ARG002 + JobConfig | None, + "Default configuration for the batch.", + ] = None, + ) -> Generator[UpdatePayload, None, None]: + """ + Submit a batch of workloads synchronously. + + This method wraps the core async execution pathway, offloading iteration to a + background thread to insulate the GIL. + + TODO: + - Implement `loop.run_in_executor()` logic to handle the sync generator. + - Bridge the async generator yields back to this blocking generator. + """ + # Stubbed yield to satisfy the generator signature + yield cast("UpdatePayload", NotImplemented) diff --git a/src/rustarium/compat.py b/src/rustarium/compat.py index 9481eb9..51f4574 100644 --- a/src/rustarium/compat.py +++ b/src/rustarium/compat.py @@ -13,12 +13,15 @@ import types from typing import Annotated +_opentelemetry_trace: types.ModuleType | None try: - from opentelemetry import ( - trace as _opentelemetry_trace, # type: ignore[import-not-found] + from opentelemetry import ( # type: ignore[import-not-found, unused-ignore] + trace as _opentelemetry_trace_mod, ) + + _opentelemetry_trace = _opentelemetry_trace_mod except ImportError: - _opentelemetry_trace = None # type: ignore[assignment] + _opentelemetry_trace = None __all__ = ["opentelemetry_trace"] diff --git a/src/rustarium/exceptions.py b/src/rustarium/exceptions.py new file mode 100644 index 0000000..f25da2f --- /dev/null +++ b/src/rustarium/exceptions.py @@ -0,0 +1,47 @@ +""" +Core exception hierarchy for the rustarium package. +""" + +from __future__ import annotations + +__all__ = [ + "ConfigurationError", + "OrchestrationError", + "RustariumError", + "SecurityError", + "WorkerError", +] + + +class RustariumError(Exception): + """ + Base exception class for all Rustarium errors. + """ + + +class ConfigurationError(RustariumError): + """ + Exception raised for invalid constraints, unparseable objects, or bad + environment state. + """ + + +class OrchestrationError(RustariumError): + """ + Exception raised for general engine failures, communication timeouts, + or scheduling issues. + """ + + +class WorkerError(RustariumError): + """ + Exception raised for process crashes, user-code exceptions, or + application-level panics. + """ + + +class SecurityError(RustariumError): + """ + Exception raised for isolation violations such as attempting a SandboxEscape, + or breaching ceilings. + """ diff --git a/src/rustarium/logging.py b/src/rustarium/logging.py index 7721126..65588e8 100644 --- a/src/rustarium/logging.py +++ b/src/rustarium/logging.py @@ -230,7 +230,7 @@ def final_filter(record: dict[str, Any]) -> bool: else: final_filter = None if filter_val is False else filter_val # type: ignore[assignment] - _HANDLER_ID = logger.add( + _HANDLER_ID = logger.add( # ty: ignore[no-matching-overload] settings.sink, # type: ignore[arg-type] level=settings.level, filter=final_filter, # type: ignore[arg-type] diff --git a/src/rustarium/schemas/__init__.py b/src/rustarium/schemas/__init__.py new file mode 100644 index 0000000..5f6547a --- /dev/null +++ b/src/rustarium/schemas/__init__.py @@ -0,0 +1,37 @@ +""" +Data schemas and models for the rustarium package. +""" + +from __future__ import annotations + +from .config import ( + ConcurrentConfig, + ConstantRateConfig, + JobConfig, + PoissonConfig, + SecurityProfile, + ViolationPolicy, + Workload, +) +from .updates import ( + JobContext, + Metrics, + RustariumContext, + UpdatePayload, + WorkloadStatus, +) + +__all__ = [ + "ConcurrentConfig", + "ConstantRateConfig", + "JobConfig", + "JobContext", + "Metrics", + "PoissonConfig", + "RustariumContext", + "SecurityProfile", + "UpdatePayload", + "ViolationPolicy", + "Workload", + "WorkloadStatus", +] diff --git a/src/rustarium/schemas/config.py b/src/rustarium/schemas/config.py new file mode 100644 index 0000000..05346c0 --- /dev/null +++ b/src/rustarium/schemas/config.py @@ -0,0 +1,166 @@ +""" +Configuration schemas for the rustarium package. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal + +from pydantic import BaseModel, Field + +__all__ = [ + "ConcurrentConfig", + "ConstantRateConfig", + "JobConfig", + "PoissonConfig", + "SecurityProfile", + "ViolationPolicy", + "Workload", +] + + +SecurityProfile = Literal["SECURE_ISOLATED", "HIGH_PERFORMANCE_TRUSTED"] +ViolationPolicy = Literal["GRACEFUL", "SIGKILL"] + + +class JobConfig(BaseModel): + """ + Configuration policy for a submitted batch of workloads. + """ + + min_workers: Annotated[ + int, + Field(default=1, description="Minimum number of worker processes."), + ] + max_workers: Annotated[ + int, + Field(default=1, description="Maximum number of worker processes."), + ] + allow_process_reuse: Annotated[ + bool, + Field( + default=False, + description="If True, idle workers can be cached and reused.", + ), + ] + + # Environment + env_vars: Annotated[ + dict[str, str], + Field( + default_factory=dict, + description="Environment variables exposed to the sandbox.", + ), + ] + mask_patterns: Annotated[ + list[str], + Field( + default_factory=list, + description="Regex patterns to scrub from stdout/stderr.", + ), + ] + + # Access + filesystem_access: Annotated[ + bool | list[str], + Field( + default=False, + description="Allowed filesystem paths or a boolean for complete access.", + ), + ] + network_access: Annotated[ + bool | list[str], + Field( + default=False, + description="Allowed network routes or a boolean for complete access.", + ), + ] + + # Security + security_profile: Annotated[ + SecurityProfile, + Field( + default="SECURE_ISOLATED", + description="The isolation tier and capability matrix to enforce.", + ), + ] + + # Limits + cpu_time_limit_ms: Annotated[ + int | None, + Field(default=None, description="Maximum allowed CPU time in milliseconds."), + ] + wall_time_limit_ms: Annotated[ + int | None, + Field(default=None, description="Max elapsed real-world time in milliseconds."), + ] + max_memory_bytes: Annotated[ + int | None, + Field(default=None, description="Maximum memory consumption ceiling in bytes."), + ] + max_errors: Annotated[ + int | None, + Field(default=None, description="Allowed failures before abort."), + ] + max_error_rate: Annotated[ + float | None, + Field(default=None, description="Ratio of allowed failures before abortion."), + ] + violation_policy: Annotated[ + ViolationPolicy, + Field( + default="SIGKILL", + description="The enforcement policy for limit breaches.", + ), + ] + + +class Workload(BaseModel): + """ + Represents a single unit of execution. + """ + + payload: Annotated[Any, Field(description="The arbitrary Python object or data.")] + override_config: Annotated[ + JobConfig | None, + Field( + default=None, + description="Workload-specific overrides for the overarching JobConfig.", + ), + ] + + +class ConcurrentConfig(BaseModel): + """ + Strategy for executing workloads concurrently. + """ + + target_workloads: Annotated[ + int, + Field( + default=1, + description="The active number of workloads that should run concurrently.", + ), + ] + + +class ConstantRateConfig(BaseModel): + """ + Strategy for executing workloads at a constant rate over time. + """ + + tasks_per_second: Annotated[ + float, + Field(description="Target rate of workload execution per second."), + ] + + +class PoissonConfig(BaseModel): + """ + Strategy for executing workloads following a Poisson distribution. + """ + + arrival_rate_lambda: Annotated[ + float, + Field(description="The average rate of incoming tasks (lambda)."), + ] diff --git a/src/rustarium/schemas/updates.py b/src/rustarium/schemas/updates.py new file mode 100644 index 0000000..e778fc3 --- /dev/null +++ b/src/rustarium/schemas/updates.py @@ -0,0 +1,106 @@ +""" +Update payload schemas for the rustarium package. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal + +from pydantic import BaseModel, Field + +__all__ = [ + "JobContext", + "Metrics", + "RustariumContext", + "UpdatePayload", + "WorkloadStatus", +] + + +class JobContext(BaseModel): + """ + Overall job-level metadata and aggregated metrics. + """ + + job_id: Annotated[str, Field(description="Unique identifier for the job.")] + total_workloads: Annotated[ + int, Field(description="Total number of workloads processed so far.") + ] + total_failures: Annotated[ + int, Field(description="Total number of failed workloads so far.") + ] + status: Annotated[ + Literal["Submitted", "Running", "Draining", "Completed", "Aborted"], + Field(description="Current state of the job."), + ] + + +class RustariumContext(BaseModel): + """ + System-level orchestration health and state. + """ + + active_workers: Annotated[ + int, Field(description="Number of currently active worker processes.") + ] + orchestrator_cpu_usage: Annotated[ + float, Field(description="CPU usage percentage of the orchestrator daemon.") + ] + orchestrator_memory_bytes: Annotated[ + int, Field(description="Memory consumption of the daemon in bytes.") + ] + + +class WorkloadStatus(BaseModel): + """ + Status transitions for a specific workload. + """ + + workload_id: Annotated[str, Field(description="Unique ID for the workload.")] + worker_id: Annotated[ + str | None, + Field( + default=None, + description="Identifier of the worker processing this workload.", + ), + ] + state: Annotated[ + Literal["Pending", "Ready", "Executing", "Throttled", "Terminated", "Failed"], + Field(description="Current execution state of the workload/worker."), + ] + + +class Metrics(BaseModel): + """ + Telemetry, latency, and resource statistics tied to a workload's execution. + """ + + execution_latency_ms: Annotated[ + float | None, Field(default=None, description="Latency of workload execution.") + ] + peak_memory_bytes: Annotated[ + int | None, Field(default=None, description="Peak memory used in execution.") + ] + custom_metrics: Annotated[ + dict[str, float], + Field(default_factory=dict, description="Custom metrics reported by workload."), + ] + + +class UpdatePayload(BaseModel): + """ + Unified payload yielded during the update stream of a job submission. + """ + + job_context: Annotated[JobContext, Field(description="Overall job-level metadata.")] + rustarium_context: Annotated[ + RustariumContext, Field(description="System-level orchestration health.") + ] + workload_status: Annotated[ + WorkloadStatus, Field(description="Status transitions for this workload.") + ] + metrics: Annotated[Metrics, Field(description="Telemetry and resource statistics.")] + result: Annotated[ + Any | None, + Field(default=None, description="The actual execution output (if completed)."), + ] diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 0000000..034241d --- /dev/null +++ b/taplo.toml @@ -0,0 +1,25 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include = [ + "*.toml", + ".devcontainer/**/*.toml", + ".github/**/*.toml", + "crates/**/*.toml", + "docs/**/*.toml", + "examples/**/*.toml", + "scripts/**/*.toml", + "src/**/*.toml", + "tests/**/*.toml", +] diff --git a/tests/README.md b/tests/README.md index f9fd517..f762a9e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,11 +6,9 @@ This directory contains the testing suite for `rustarium`. We use `pytest` as ou Tests are categorized into three distinct tiers, each located in its respective subdirectory: -| Test Tier | Directory | Description | -| :-------------- | :------------------- | :------------------------------------------------------------------------------------------------------------------------ | -| **Unit** | `tests/unit/` | Fast, isolated tests for individual functions and classes. These tests should not rely on external services or databases. | -| **Integration** | `tests/integration/` | Slower tests that verify interactions between multiple components or modules within the application. | -| **End-to-End** | `tests/e2e/` | Full-stack tests simulating real user workflows, from entry points to expected outcomes. | +| **Unit** | `tests/python/unit/` | Fast, isolated tests for individual functions and classes. These tests should not rely on external services or systems. | +| **Integration** | `tests/python/integration/` | Slower tests that verify interactions between multiple components or modules within the application. | +| **End-to-End** | `tests/e2e/` | Full-stack tests simulating real user workflows, from entry points to expected outcomes. | ## Pytest Markers @@ -32,17 +30,29 @@ We recommend using `hatch` to run tests, as it automatically manages the require ### Standard Test Runs ```bash -# Run all tests -hatch run test:all +# Run all Python and Rust tests (global cascade) +hatch run all:tests -# Run only unit tests -hatch run test:unit +# Run all Python tests +hatch run python:tests -# Run tests with a specific marker -hatch run test:all -m "smoke" +# Run only Python unit tests +hatch run python:tests-unit -# Run tests in a specific file -hatch run test:all tests/unit/test_version.py +# Run Python integration tests +hatch run python:tests-int + +# Run Python tests with a specific marker +hatch run python:tests -m "smoke" + +# Run Python tests in a specific file +hatch run python:tests tests/python/unit/test_version.py + +# Run Rust unit and integration tests +hatch run rust:tests + +# Run system end-to-end (e2e) tests +hatch run project:tests-e2e ``` ### Coverage Reports @@ -50,19 +60,24 @@ hatch run test:all tests/unit/test_version.py To generate coverage reports, use the `-cov` suffixed commands. These will output both a terminal report and an HTML report located in `docs/coverage/`. ```bash -# Run all tests with coverage -hatch run test:all-cov +# Run all Python tests with coverage +hatch run python:tests-cov + +# Run only Python unit tests with coverage +hatch run python:tests-unit-cov -# Run only unit tests with coverage -hatch run test:unit-cov +# Run all Rust tests with coverage +hatch run rust:tests-cov ``` ## Adding New Tests -When creating new tests, ensure they are placed in the appropriate tier directory (`unit/`, `integration/`, or `e2e/`) and include the necessary markers. +When creating new tests, ensure they are placed in the appropriate tier directory (`python/unit/`, `python/integration/`, or `e2e/`) and include the necessary markers. ### Example Unit Test + + ```python """Unit tests for my_module.""" @@ -81,4 +96,4 @@ def test_my_function() -> None: ``` > [!TIP] -> **Type Hints:** Ensure all test functions are fully type-hinted (e.g., `-> None:` for test return types) to satisfy our strict `mypy` configuration. +> **Type Hints:** Ensure all test functions are fully type-hinted (e.g., `-> None:` for test return types) to satisfy our strict type-checking configuration. diff --git a/tests/integration/__init__.py b/tests/python/integration/__init__.py similarity index 100% rename from tests/integration/__init__.py rename to tests/python/integration/__init__.py diff --git a/tests/integration/test_integration.py b/tests/python/integration/test_integration.py similarity index 70% rename from tests/integration/test_integration.py rename to tests/python/integration/test_integration.py index 75c3823..653b2a3 100644 --- a/tests/integration/test_integration.py +++ b/tests/python/integration/test_integration.py @@ -15,7 +15,6 @@ __BUILD_METADATA__, __GIT_METADATA__, __VERSION_METADATA__, - __version__, ) @@ -40,6 +39,7 @@ def test_invocation( def mock_configure(settings: LoggingSettings | None = None) -> None: if settings is None: settings = LoggingSettings() + settings.level = "DEBUG" settings.sink = capture_sink settings.filter = False settings.enqueue = False @@ -53,9 +53,36 @@ def mock_configure(settings: LoggingSettings | None = None) -> None: assert exc_info.value.code == 0 output = capture_sink.getvalue() - assert __version__ in output + assert "Hello from rustarium" in output assert "Settings:" in output + @pytest.mark.smoke + def test_invocation_with_command( + self, monkeypatch: pytest.MonkeyPatch, capture_sink: StringIO + ) -> None: + """Ensure that executing a subcommand does not print default callback info.""" + original_configure = configure_logger + + def mock_configure(settings: LoggingSettings | None = None) -> None: + if settings is None: + settings = LoggingSettings() + settings.level = "DEBUG" + settings.sink = capture_sink + settings.filter = False + settings.enqueue = False + original_configure(settings) + + monkeypatch.setattr("rustarium.__main__.configure_logger", mock_configure) + monkeypatch.setattr(sys, "argv", ["rustarium", "diagnose"]) + + with pytest.raises(SystemExit) as exc_info: + main() + + assert exc_info.value.code == 0 + output = capture_sink.getvalue() + assert "Running Rustarium diagnostics..." in output + assert "Settings:" not in output + class TestConfigureLogger: """Integration tests for the configure_logger function.""" @@ -111,3 +138,11 @@ def test_git_metadata() -> None: def test_build_metadata() -> None: """A regression test to ensure BUILD_METADATA is fully populated.""" assert isinstance(__BUILD_METADATA__.timestamp, str) + + +@pytest.mark.smoke +def test_rust_bindings() -> None: + """Test that Rust PyO3 bindings are importable and work as expected.""" + from rustarium import sum_as_string # noqa: PLC0415 + + assert sum_as_string(5, 7) == "12" diff --git a/tests/unit/__init__.py b/tests/python/unit/__init__.py similarity index 100% rename from tests/unit/__init__.py rename to tests/python/unit/__init__.py diff --git a/tests/unit/test_compat.py b/tests/python/unit/test_compat.py similarity index 100% rename from tests/unit/test_compat.py rename to tests/python/unit/test_compat.py diff --git a/tests/unit/test_init.py b/tests/python/unit/test_init.py similarity index 50% rename from tests/unit/test_init.py rename to tests/python/unit/test_init.py index 9aee005..aa840db 100644 --- a/tests/unit/test_init.py +++ b/tests/python/unit/test_init.py @@ -11,11 +11,31 @@ def test___all__() -> None: """Verify that the root __init__.py correctly exposes expected API.""" expected_exports = [ + "AsyncRustariumClient", + "ConcurrentConfig", + "ConfigurationError", + "ConstantRateConfig", + "JobConfig", + "JobContext", "LoggingSettings", + "Metrics", + "OrchestrationError", + "PoissonConfig", + "RustariumClient", + "RustariumContext", + "RustariumError", + "SecurityError", + "SecurityProfile", "Settings", + "UpdatePayload", + "ViolationPolicy", + "WorkerError", + "Workload", + "WorkloadStatus", "__version__", "configure_logger", "logger", + "sum_as_string", ] assert rustarium.__all__ == expected_exports for export_name in expected_exports: diff --git a/tests/unit/test_logging.py b/tests/python/unit/test_logging.py similarity index 100% rename from tests/unit/test_logging.py rename to tests/python/unit/test_logging.py diff --git a/tests/unit/test_settings.py b/tests/python/unit/test_settings.py similarity index 98% rename from tests/unit/test_settings.py rename to tests/python/unit/test_settings.py index d40a50c..6ceadef 100644 --- a/tests/unit/test_settings.py +++ b/tests/python/unit/test_settings.py @@ -43,7 +43,7 @@ def test_initialization(self, valid_instances: Settings) -> None: def test_invalid_initialization_values(self) -> None: """Test initialization with invalid values fails validation.""" with pytest.raises(ValidationError): - Settings(environment="invalid_env") # type: ignore[arg-type] + Settings(environment="invalid_env") # type: ignore[arg-type] # ty: ignore[invalid-argument-type] @pytest.mark.regression def test_marshalling(self, valid_instances: Settings) -> None: diff --git a/tests/unit/test_version.py b/tests/python/unit/test_version.py similarity index 100% rename from tests/unit/test_version.py rename to tests/python/unit/test_version.py diff --git a/uv.lock b/uv.lock index acb5885..dc0cd71 100644 --- a/uv.lock +++ b/uv.lock @@ -2,8 +2,172 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.15'", - "python_full_version < '3.15'", + "python_full_version >= '3.14'", + "python_full_version >= '3.11' and python_full_version < '3.14'", + "python_full_version < '3.11'", +] + +[[package]] +name = "aiodns" +version = "4.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycares" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/22/a2d928e0e42baad0471d12ec44c71152ac870486e8298dddb2893b888c29/aiodns-4.0.4.tar.gz", hash = "sha256:cb10e0c0d2591636716ad2fe402e977c16d71bdaf76bb8cb49e8a6633596f736", size = 29918, upload-time = "2026-05-20T01:54:15.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/70/72e4ab117425ccdc4d10bd523a94c1baa051a15586057d64a4c6888f9e3f/aiodns-4.0.4-py3-none-any.whl", hash = "sha256:c24dd605bac70a1676ce503f967a98483ff163507198557d8e9db16267e6cfd2", size = 12696, upload-time = "2026-05-20T01:54:14.134Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/85/cebc47ee74d8b408749073a1a46c6fcba13d170dc8af7e61996c6c9394ac/aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b", size = 750547, upload-time = "2026-03-31T21:56:30.024Z" }, + { url = "https://files.pythonhosted.org/packages/05/98/afd308e35b9d3d8c9ec54c0918f1d722c86dc17ddfec272fcdbcce5a3124/aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5", size = 503535, upload-time = "2026-03-31T21:56:31.935Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4d/926c183e06b09d5270a309eb50fbde7b09782bfd305dec1e800f329834fb/aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670", size = 497830, upload-time = "2026-03-31T21:56:33.654Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d6/f47d1c690f115a5c2a5e8938cce4a232a5be9aac5c5fb2647efcbbbda333/aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274", size = 1682474, upload-time = "2026-03-31T21:56:35.513Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/056fd37b1bb52eac760303e5196acc74d9d546631b035704ae5927f7b4ac/aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a", size = 1655259, upload-time = "2026-03-31T21:56:37.843Z" }, + { url = "https://files.pythonhosted.org/packages/91/9f/78eb1a20c1c28ae02f6a3c0f4d7b0dcc66abce5290cadd53d78ce3084175/aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d", size = 1736204, upload-time = "2026-03-31T21:56:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/de/6c/d20d7de23f0b52b8c1d9e2033b2db1ac4dacbb470bb74c56de0f5f86bb4f/aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796", size = 1826198, upload-time = "2026-03-31T21:56:41.378Z" }, + { url = "https://files.pythonhosted.org/packages/2f/86/a6f3ff1fd795f49545a7c74b2c92f62729135d73e7e4055bf74da5a26c82/aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95", size = 1681329, upload-time = "2026-03-31T21:56:43.374Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/84cd3dab6b7b4f3e6fe9459a961acb142aaab846417f6e8905110d7027e5/aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5", size = 1560023, upload-time = "2026-03-31T21:56:45.031Z" }, + { url = "https://files.pythonhosted.org/packages/41/2c/db61b64b0249e30f954a65ab4cb4970ced57544b1de2e3c98ee5dc24165f/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a", size = 1652372, upload-time = "2026-03-31T21:56:47.075Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/e96988a6c982d047810c772e28c43c64c300c943b0ed5c1c0c4ce1e1027c/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73", size = 1662031, upload-time = "2026-03-31T21:56:48.835Z" }, + { url = "https://files.pythonhosted.org/packages/b7/26/a56feace81f3d347b4052403a9d03754a0ab23f7940780dada0849a38c92/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297", size = 1708118, upload-time = "2026-03-31T21:56:50.833Z" }, + { url = "https://files.pythonhosted.org/packages/78/6e/b6173a8ff03d01d5e1a694bc06764b5dad1df2d4ed8f0ceec12bb3277936/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074", size = 1548667, upload-time = "2026-03-31T21:56:52.81Z" }, + { url = "https://files.pythonhosted.org/packages/16/13/13296ffe2c132d888b3fe2c195c8b9c0c24c89c3fa5cc2c44464dc23b22e/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e", size = 1724490, upload-time = "2026-03-31T21:56:54.541Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1f1c287f4a79782ef36e5a6e62954c85343bc30470d862d30bd5f26c9fa2/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7", size = 1667109, upload-time = "2026-03-31T21:56:56.21Z" }, + { url = "https://files.pythonhosted.org/packages/ef/42/8461a2aaf60a8f4ea4549a4056be36b904b0eb03d97ca9a8a2604681a500/aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9", size = 439478, upload-time = "2026-03-31T21:56:58.292Z" }, + { url = "https://files.pythonhosted.org/packages/e5/71/06956304cb5ee439dfe8d86e1b2e70088bd88ed1ced1f42fb29e5d855f0e/aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76", size = 462047, upload-time = "2026-03-31T21:57:00.257Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/a20c4ac64aeaef1679e25c9983573618ff765d7aa829fa2b84ae7573169e/aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6", size = 757513, upload-time = "2026-03-31T21:57:02.146Z" }, + { url = "https://files.pythonhosted.org/packages/75/0a/39fa6c6b179b53fcb3e4b3d2b6d6cad0180854eda17060c7218540102bef/aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d", size = 506748, upload-time = "2026-03-31T21:57:04.275Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c", size = 501673, upload-time = "2026-03-31T21:57:06.208Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/3bc7525d7e2beaa11b309a70d48b0d3cfc3c2089ec6a7d0820d59c657053/aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb", size = 1763757, upload-time = "2026-03-31T21:57:07.882Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ab/e87744cf18f1bd78263aba24924d4953b41086bd3a31d22452378e9028a0/aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6", size = 1720152, upload-time = "2026-03-31T21:57:09.946Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/ed17a6f2d742af17b50bae2d152315ed1b164b07a5fd5cc1754d99e4dfa5/aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13", size = 1818010, upload-time = "2026-03-31T21:57:12.157Z" }, + { url = "https://files.pythonhosted.org/packages/53/06/ecbc63dc937192e2a5cb46df4d3edb21deb8225535818802f210a6ea5816/aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174", size = 1907251, upload-time = "2026-03-31T21:57:14.023Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc", size = 1759969, upload-time = "2026-03-31T21:57:16.146Z" }, + { url = "https://files.pythonhosted.org/packages/f6/78/a38f8c9105199dd3b9706745865a8a59d0041b6be0ca0cc4b2ccf1bab374/aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6", size = 1616871, upload-time = "2026-03-31T21:57:17.856Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/27392a61ead8ab38072105c71aa44ff891e71653fe53d576a7067da2b4e8/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49", size = 1739844, upload-time = "2026-03-31T21:57:19.679Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/5564e7ae26d94f3214250009a0b1c65a0c6af4bf88924ccb6fdab901de28/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8", size = 1731969, upload-time = "2026-03-31T21:57:22.006Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/705a3929149865fc941bcbdd1047b238e4a72bcb215a9b16b9d7a2e8d992/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d", size = 1795193, upload-time = "2026-03-31T21:57:24.256Z" }, + { url = "https://files.pythonhosted.org/packages/a6/19/edabed62f718d02cff7231ca0db4ef1c72504235bc467f7b67adb1679f48/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c", size = 1606477, upload-time = "2026-03-31T21:57:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/de/fc/76f80ef008675637d88d0b21584596dc27410a990b0918cb1e5776545b5b/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac", size = 1813198, upload-time = "2026-03-31T21:57:28.316Z" }, + { url = "https://files.pythonhosted.org/packages/e5/67/5b3ac26b80adb20ea541c487f73730dc8fa107d632c998f25bbbab98fcda/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3", size = 1752321, upload-time = "2026-03-31T21:57:30.549Z" }, + { url = "https://files.pythonhosted.org/packages/88/06/e4a2e49255ea23fa4feeb5ab092d90240d927c15e47b5b5c48dff5a9ce29/aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06", size = 439069, upload-time = "2026-03-31T21:57:32.388Z" }, + { url = "https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8", size = 462859, upload-time = "2026-03-31T21:57:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" }, + { url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" }, + { url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" }, + { url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" }, + { url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" }, + { url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" }, + { url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" }, + { url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" }, + { url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" }, + { url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" }, + { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" }, + { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" }, + { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" }, + { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" }, + { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" }, + { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" }, + { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" }, + { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" }, + { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" }, + { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" }, + { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" }, +] + +[[package]] +name = "aiomultiprocess" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/d4/1e69e17dda5df91734b70d03dbbf9f222ddb438e1f3bf4ea8fa135ce46de/aiomultiprocess-0.9.1.tar.gz", hash = "sha256:f0231dbe0291e15325d7896ebeae0002d95a4f2675426ca05eb35f24c60e495b", size = 24514, upload-time = "2024-04-23T08:26:04.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/14/c48c2f5c96960f5649a72b96a0a31d45384b37d89a63f7ccea76bf4fceba/aiomultiprocess-0.9.1-py3-none-any.whl", hash = "sha256:3a7b3bb3c38dbfb4d9d1194ece5934b6d32cf0280e8edbe64a7d215bba1322c6", size = 17517, upload-time = "2024-04-23T08:26:01.649Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] [[package]] @@ -39,50 +203,30 @@ wheels = [ ] [[package]] -name = "ast-serialize" -version = "0.3.0" +name = "argcomplete" +version = "3.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/9d/912fefab0e30aee6a3af8a62bbea4a81b29afa4ba2c973d31170620a26de/ast_serialize-0.3.0.tar.gz", hash = "sha256:1bc3ca09a63a021376527c4e938deedd11d11d675ce850e6f9c7487f5889992b", size = 60689, upload-time = "2026-04-30T23:24:48.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/57/a54d4de491d6cdd7a4e4b0952cc3ca9f60dcefa7b5fb48d6d492debe1649/ast_serialize-0.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a867927df59f76a18dc1d874a0b2c079b42c58972dca637905576deb0912e14", size = 1182966, upload-time = "2026-04-30T23:23:57.376Z" }, - { url = "https://files.pythonhosted.org/packages/ee/9e/a5db014bb0f91b209236b57c429389e31290c0093532b8436d577699b2fa/ast_serialize-0.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a6fb063bf040abf8321e7b8113a0554eda445ffc508aa51287f8808886a5ae22", size = 1171316, upload-time = "2026-04-30T23:23:59.63Z" }, - { url = "https://files.pythonhosted.org/packages/15/59/fd55133e478c4326f60a11df02573bf7ccb2ac685810b50f1803d0f68053/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5075cd8482573d743586779e5f9b652a015e37d4e95132d7e5a9bc5c8f483d8f", size = 1232234, upload-time = "2026-04-30T23:24:01.168Z" }, - { url = "https://files.pythonhosted.org/packages/cc/79/0ca1d26357ecb4a697d74d00b73ef3137f24c140424125393a0de820eb09/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41560b27794f4553b0f77811e9fb325b77db4a2b39018d437e09932275306e66", size = 1233437, upload-time = "2026-04-30T23:24:03.151Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/7078ec94dd6e124b8e028ac77016a4f13c83fa1c145790f2e68f3816998b/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b967c01ca74909c5d90e0fe4393401e2cc5da5ebd9a6262a19e45ffd3757dec8", size = 1440188, upload-time = "2026-04-30T23:24:04.717Z" }, - { url = "https://files.pythonhosted.org/packages/21/16/cca7195ef55a012f8013c3442afa91d287a0a36dcf88b480b262475135b3/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:424ebb8f46cd993f7cec4009d119312d8433dd90e6b0df0499cd2c91bdcc5af9", size = 1254211, upload-time = "2026-04-30T23:24:06.18Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0f/f3d4dfae67dee6580534361a6343367d34217e7d25cff858bd1d8f03b8ed/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14b1d566b56e2ee70b11fec1de7e0b94ec7cd83717ec7d189967841a361190e", size = 1255973, upload-time = "2026-04-30T23:24:07.772Z" }, - { url = "https://files.pythonhosted.org/packages/14/41/55fbfe02c42f40fbe3e74eda167d977d555ff720ce1abfa08515236efd88/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba30b18735f047ec11103d1ab92f4789cf1fea1e0dc89b04a2f5a0632fd79de", size = 1298629, upload-time = "2026-04-30T23:24:09.4Z" }, - { url = "https://files.pythonhosted.org/packages/28/36/7d2501cacc7989fb8504aa9da2a2022a174200a59d4e6639de4367a57fdd/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ea0754cb7b0f682ebb005ffb0d18f8d17993490d9c289863cd69cacc4ab8df", size = 1408435, upload-time = "2026-04-30T23:24:11.013Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/54e3b469c3fa0bf9cd532fa643d1d33b73303f8d70beac3e366b68dd64b7/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a0c5aa1073a5ba7b2abaa4b54abe8b8d75c4d1e2d54a2ff70b0ca6222fea5728", size = 1508174, upload-time = "2026-04-30T23:24:12.635Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/9b9621865b02c60539e26d9b114a312b4fa46aa703e33e79317174bfea21/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e52650d834c1ea7791969a361de2c54c13b2fb4c519ec79445fa8b9021a147d", size = 1502354, upload-time = "2026-04-30T23:24:14.186Z" }, - { url = "https://files.pythonhosted.org/packages/34/dd/f138bc5c43b0c414fdd12eefe15677839323078b6e75301ad7f96cd26d45/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15bd6af3f136c61dae27805eb6b8f3269e85a545c4c27ffe9e530ead78d2b36d", size = 1450504, upload-time = "2026-04-30T23:24:16.076Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/97ef9e1c315601db74365955c8edd3292e3055500d6317602815dbdf08ae/ast_serialize-0.3.0-cp314-cp314t-win32.whl", hash = "sha256:d188bfe37b674b49708497683051d4b571366a668799c9b8e8a94513694969d9", size = 1058662, upload-time = "2026-04-30T23:24:17.535Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d6/e2c3483c31580fdb623f92ad38d2f856cde4b9205a3e6bd84760f3de7d82/ast_serialize-0.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5832c2fdf8f8a6cf682b4cfcf677f5eaf39b4ddbc490f5480cfccdd1e7ce8fa1", size = 1100349, upload-time = "2026-04-30T23:24:18.992Z" }, - { url = "https://files.pythonhosted.org/packages/ab/89/29abcb1fe18a429cda60c6e0bbd1d6e90499339842a2f548d7567542357e/ast_serialize-0.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:670f177188d128fb7f9f15b5ad0e1b553d22c34e3f584dcb83eb8077600437f0", size = 1072895, upload-time = "2026-04-30T23:24:20.706Z" }, - { url = "https://files.pythonhosted.org/packages/bc/93/72abad83966ed6235647c9f956417dc1e17e997696388521910e3d1fa3f4/ast_serialize-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ec2fafa5e4313cc8feed96e436ebe19ac7bc6fa41fbc2827e826c48b9e4c3a9", size = 1190024, upload-time = "2026-04-30T23:24:22.486Z" }, - { url = "https://files.pythonhosted.org/packages/85/4f/eb88584b2f0234e581762011208ca203252bf6c98e59b4769daa571f3576/ast_serialize-0.3.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef6d3c08b7b4cd29b48410338e134764a00e76d25841eb02c1084e868c888ecc", size = 1178633, upload-time = "2026-04-30T23:24:24.35Z" }, - { url = "https://files.pythonhosted.org/packages/56/51/cf1ec1ff3e616373d0dcbd5fad502e0029dc541f13ab642259762a7d127f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d841424f41b886e98044abc80769c14a956e6e5ccd5fb5b0d9f5ead72be18a4", size = 1241351, upload-time = "2026-04-30T23:24:25.987Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/68fcf50478cf1093f2d423f034ae06453122c8b415d8e21a44668eca485d/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d21453734ad39367ede5d37efe4f59f830ce1c09f432fc72a90e368f77a4a3e7", size = 1239582, upload-time = "2026-04-30T23:24:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/9d/c1/a6c9fa284eceb5fc6f21347e968445a051d7ca2c4d34e6a04314646dbcee/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5e110cdce2a347e1dd987529c88ef54d26f67848dce3eba1b3b2cc2cf085c94", size = 1448853, upload-time = "2026-04-30T23:24:29.534Z" }, - { url = "https://files.pythonhosted.org/packages/23/5f/8ad3829a09e4e8c5328a53ce7d4711d660944e3e164c5f6abcc2c8f27167/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6e23a98e57560a055f5c4b68700a0fd5ce483d2814c23140b3638c7f5d1e61", size = 1262204, upload-time = "2026-04-30T23:24:31.482Z" }, - { url = "https://files.pythonhosted.org/packages/25/13/44aa28d97f10e25247e8576b5f6b2795d4fa1a80acc88acc942c508d06f7/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c9e763d70293d65ce1e1ea8c943140c68d0953f0268c7ee0998f2e07f77dd0", size = 1266458, upload-time = "2026-04-30T23:24:33.088Z" }, - { url = "https://files.pythonhosted.org/packages/d8/58/b3a8be3777cd3744324fd5cec0d80d37cd96fc7cbb0fb010e03dff1e870f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4388a1796c228f1ce5c391426f7d21a0003ad3b47f677dbeded9bd1a85c7209f", size = 1308700, upload-time = "2026-04-30T23:24:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/13/03/f8312d6b57f5471a9dc7946f22b8798a1fc296d38c25766223aacadec42c/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5283cdcc0c64c3d8b9b688dc6aaa012d9c0cf1380a7f774a6bae6a1c01b3205a", size = 1416724, upload-time = "2026-04-30T23:24:36.562Z" }, - { url = "https://files.pythonhosted.org/packages/50/5d/13fc3789a7abac00559da2e2e9f386db4612aa1f84fc53d09bf714c37545/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ef88cc5842a5d7a6ac09dc0d5fc2c98f5d276c1f076f866d55047ce886785b", size = 1515441, upload-time = "2026-04-30T23:24:38.018Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b9/7ab43fc7a23b1f970281093228f5f79bed6edeed7a3e672bde6d7a832a58/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cc14bf402bdc0978594ecce783793de2c7470cd4f5cd7eb286ca97ed8ff7cba9", size = 1510522, upload-time = "2026-04-30T23:24:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/56/ec/d75fc2b788d319f1fad77c14156896f31afdfc68af85b505e5bdebcb9592/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11eae0cf1b7b3e0678133cc2daa974ea972caf02eb4b3aa062af6fa9acd52c57", size = 1460917, upload-time = "2026-04-30T23:24:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/95/74/f99c81193a2725911e1911ae567ed27c2f2419332c7f3537366f9d238cac/ast_serialize-0.3.0-cp39-abi3-win32.whl", hash = "sha256:2db3dd99de5e6a5a11d7dda73de8750eb6e5baaf25245adf7bdcfe64b6108ae2", size = 1067804, upload-time = "2026-04-30T23:24:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/16/81/76af00c47daa151e89f98ae21fbbcb2840aaa9f5766579c4da76a3c57188/ast_serialize-0.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:a2cd125adccf7969470621905d302750cd25951f22ea430d9a25b7be031e5549", size = 1105561, upload-time = "2026-04-30T23:24:44.578Z" }, - { url = "https://files.pythonhosted.org/packages/bd/46/d3ec57ad500f598d1554bd14ce4df615960549ab2844961bc4e1f5fbd174/ast_serialize-0.3.0-cp39-abi3-win_arm64.whl", hash = "sha256:0dd00da29985f15f50dc35728b7e1e7c84507bccfea1d9914738530f1c72238a", size = 1077165, upload-time = "2026-04-30T23:24:46.377Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] [[package]] -name = "babel" -version = "2.18.0" +name = "attrs" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -95,34 +239,348 @@ wheels = [ ] [[package]] -name = "backrefs" -version = "7.0" +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, +] + +[[package]] +name = "backports-zstd" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/05/480d439b482edf59b786bc19b474d990c61942e372f5de3dc14acac8154d/backports_zstd-1.5.0.tar.gz", hash = "sha256:a5e622a82eb183b4fbe18032755ce0a15fa9a82f2adb9b621620b91247aaedb7", size = 998556, upload-time = "2026-05-11T19:54:24.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/6e/bc24b45e16381272db45bfe627c1762600fc5fbcd39cef3723c89425129e/backports_zstd-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09045a00d9dad12dab49e029b26c197637b882cf4adc737a373404ba2aaabbca", size = 436832, upload-time = "2026-05-11T19:51:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e2/87/85bc9b98bd0bbbe76af0aa19d423eb93906467110e4cdd4741fd8d26def5/backports_zstd-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e51edd66db6855bee020c951ca5c2e816777bfe77f87742fbbfae9a32d482fec", size = 363217, upload-time = "2026-05-11T19:51:56.359Z" }, + { url = "https://files.pythonhosted.org/packages/c1/61/b461cf3620ee3a55e20d885ef61c5ab56a3745ccc0d422f74968337777ca/backports_zstd-1.5.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:73ff4ceb7e28538455e0a44f53e05a731bbdb9bfe2cab4a1637dd1f0093732e3", size = 507163, upload-time = "2026-05-11T19:51:57.957Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cb/4e0063bf90d6fd17329ff271e131758d5d96a73061b6d45577a8be6ebf42/backports_zstd-1.5.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9526d69c8fbef03e04d74b33946e23f806399cb49e51550bb21d757fb2ce869", size = 476728, upload-time = "2026-05-11T19:51:59.822Z" }, + { url = "https://files.pythonhosted.org/packages/11/4a/ee0c81e24789781fcc8399817e5c82121001293dbbaf17629833ff0d34e8/backports_zstd-1.5.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5e24ee1e1bbb4549a2ad63695b4a5776596aa171fdaf7c1e178e61e351faf0a9", size = 582391, upload-time = "2026-05-11T19:52:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/e3/aa/3c2c28492656af005ed9602beab4c20813346b53257413ae57bf88adbd41/backports_zstd-1.5.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ebfbf7307d618d68deef905d3d6655339d4ce187e176023bff8fbd44ec1e20d0", size = 642040, upload-time = "2026-05-11T19:52:03.396Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ad/9070e691597657bd3b983d8c8ba46bc6ee4d394608e7be969f2060f16899/backports_zstd-1.5.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b82506a4da0977754335c727752411bbba1fe476a8662d96161218f275fba859", size = 492266, upload-time = "2026-05-11T19:52:05.16Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ec/7222e9e8ca899cf9d538468b0fb6386da93dae94f6e60625a7ef99281672/backports_zstd-1.5.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cf8355cdfa7a2cba9c51655d56e6be39c751799286b142640be30fef2301a70", size = 566215, upload-time = "2026-05-11T19:52:07.037Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f8/bf880d87cfb71ad9753142d2ad0802015ee4a343b8c080ea6f0eb6b05bfb/backports_zstd-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f7de15f3871d21d6e761c5a309618b069fee5f225e64e4406956ac0209dc6917", size = 482662, upload-time = "2026-05-11T19:52:08.726Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ed/fc7144651682744b32de1e624bcad6d0bb72d6359e37a5d9e980f3d5a45b/backports_zstd-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:624825b9c290e6089cd9955d88da04b085528fe213adf3e4e8be5c0fffef6c65", size = 510592, upload-time = "2026-05-11T19:52:10.244Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/436ee1aa915fa310d0e83c361f25757960f96ef798f532948351637125fd/backports_zstd-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7088a75f96d8f6b0d3523ec3a99d1472ce03c3524b2f7b485b80e115ef20055f", size = 586713, upload-time = "2026-05-11T19:52:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/55/9b/16573be05e8fe54cb356d9aa9aeb84d1e14fd49fe23ea7f261027e2e7f25/backports_zstd-1.5.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:97f4d29e99538b11313cbc7a6d9b3c2ce0d69fdc497699ab74953d0d5949ab88", size = 564032, upload-time = "2026-05-11T19:52:13.864Z" }, + { url = "https://files.pythonhosted.org/packages/95/33/7cf01fb8b4cff1ea6c7fee19d64de8a1a8dec7b18703af2aca79c8f87864/backports_zstd-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8b4e17632759a45a7d0c4cf31968d8d033eefbe1a3d81d8aaf519558371c3359", size = 632604, upload-time = "2026-05-11T19:52:15.469Z" }, + { url = "https://files.pythonhosted.org/packages/1a/03/547b4e0abf8e1c2f29314e1e3ed7a3e2054b22560b2bad843423fbb99140/backports_zstd-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0075195c79c0508bc7313a3402b187bd9d27d4f9a376e8e2caac0fc2baeacbdf", size = 496272, upload-time = "2026-05-11T19:52:17.064Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ff/28c94189774b62c26ddf65ee54ec3591f6f0217d9545d20854f8600541b0/backports_zstd-1.5.0-cp310-cp310-win32.whl", hash = "sha256:11c694c9eef69c19a52df94466d4fd5c8b1bdfbaad350e95adc883b40d8b3be2", size = 289665, upload-time = "2026-05-11T19:52:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/579f193d023c099ecaf560aae72701bfa6bacc5486cf57f91236b9c1404e/backports_zstd-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1ea900765329a515020e4e66c65a826657cc1f110770cac3f71ec01b43f2d25", size = 314698, upload-time = "2026-05-11T19:52:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f7/1cfc87f0171268ffb3eb479f0b8ef936164cbb6bddd1fbf1457e1ac8aecb/backports_zstd-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:0c473387025e233d123f401d09a17a57e0b9af2ec2423aae7f50f1c806887cb3", size = 291362, upload-time = "2026-05-11T19:52:22.486Z" }, + { url = "https://files.pythonhosted.org/packages/26/bc/083c0ebee316f4863ed288c4a5eaa1e98be115e82deb8855da8bab1c7701/backports_zstd-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fbaa5502617dc4f04327c7a2951f0fcdca7aaef93ddf32c15dc8b620208174fa", size = 436838, upload-time = "2026-05-11T19:52:24.349Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/bf778667fff6598dbd0791745123ed964aee94753ae8e4e92aa1e07417b6/backports_zstd-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:204f00d62e95aab987c7c019452b2373bdefb17252443765f2ede7f15b6e669a", size = 363215, upload-time = "2026-05-11T19:52:25.887Z" }, + { url = "https://files.pythonhosted.org/packages/63/a5/4fae78734dbefcb4b5386137c807e2107c4bc94e85c0d9eaae79206dde84/backports_zstd-1.5.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:2c77c0d4c330afd26d2a98f3d689ab922ec3f046014a1614ddcaad437666ac05", size = 507161, upload-time = "2026-05-11T19:52:27.48Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ec/b64409f0cf56fb65181d6f5d9130058f19d5c3c9f8c581a5e2bd62642630/backports_zstd-1.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bb2f2d2c07358edeaa251cf804b993e9f0d5d93af8a7ea2414d80ff3c105e95", size = 476728, upload-time = "2026-05-11T19:52:29.182Z" }, + { url = "https://files.pythonhosted.org/packages/4d/10/4c1693cb4e129585a6e4cb565106cad7347e61c43c8375b9e9cadb00eb06/backports_zstd-1.5.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89f554abcebcb2c487024e63be8059083775c5fd351fec0cc2dc3e9f528714", size = 582388, upload-time = "2026-05-11T19:52:30.908Z" }, + { url = "https://files.pythonhosted.org/packages/45/b9/dc748a0e7d21ce2228241f6e8af96d297c80ab69c4c49429309b8fa3beb8/backports_zstd-1.5.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea969758af743000d822fc3a69dc9de059bbbb8d07d2f13e06ff49ac63cce74f", size = 642091, upload-time = "2026-05-11T19:52:32.397Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/02366ddae6e008d53df71605e4e3ca8dcea5d1dfcba29040b46883a23127/backports_zstd-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:775ad82d268923639bc924013fc61561df376c148506b241f0f80718b5bb3a2f", size = 492256, upload-time = "2026-05-11T19:52:34.441Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/c5e7824c17abc87dbb24c7c90dc43054d701533cf04d3531cb9b7105cdac/backports_zstd-1.5.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:663128370bbc2ebcc436b8977bc434a7bf29919d92d91fee05ed6fb0fa807646", size = 566214, upload-time = "2026-05-11T19:52:35.962Z" }, + { url = "https://files.pythonhosted.org/packages/12/7b/ee7368c4ad8f5e00b3fd84fc566fb7714aa766c5672500793990e19efa00/backports_zstd-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572c76832e9a24da4084befa52c23f4c03fede2aa250ae6250cbc5a11b980f69", size = 482666, upload-time = "2026-05-11T19:52:37.675Z" }, + { url = "https://files.pythonhosted.org/packages/77/36/2826f9f04b6c91d5f707f49188ac6f5ec7487b36d73caedfa20db3307826/backports_zstd-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9410bcbcd3afd787a15a276d68f954d1703788c780faa421183a61d39da8b862", size = 510594, upload-time = "2026-05-11T19:52:39.501Z" }, + { url = "https://files.pythonhosted.org/packages/84/3b/95342baf0e301b7d06c6862389f8520a9d71f073a6c1a5b86182e7d89148/backports_zstd-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0fab15e6895bef621041dd82d6306ffa24889257dd902c4b98b88e4260b3465d", size = 586713, upload-time = "2026-05-11T19:52:41.461Z" }, + { url = "https://files.pythonhosted.org/packages/bc/32/73d2b8f572960307406b084bb8932f4ebd9fcedb05d1502e04fecf25970a/backports_zstd-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ffde637b6d0082f1c3356657002469cf199c7c12d50d9822a55b13425c778d3", size = 564037, upload-time = "2026-05-11T19:52:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a4/6e319fa7fa5851c3ca9701cbded9522c16018432a01a33a95cc0fccb6b4a/backports_zstd-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c01d377c1489cb2230bf6a9ff01c73c42863cc96ee648c49923d4f6d4ea4e2d5", size = 632626, upload-time = "2026-05-11T19:52:45.017Z" }, + { url = "https://files.pythonhosted.org/packages/67/5c/10df0444db05f9276b286d230a3d6948ad47c593fc22925b8fe551d34b26/backports_zstd-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4080bb9c8a51bb2bf8caf8018d78278cd49eb924cb06a54f56a411095e2ac912", size = 496270, upload-time = "2026-05-11T19:52:46.558Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ad/6cd1de5cd858ac653833098f13a4643a4c9db484072350d3dbf299cc46f1/backports_zstd-1.5.0-cp311-cp311-win32.whl", hash = "sha256:9f4fe3fd82c8c6e8a9fdc5c71f92f9fe2442d02e7f59fddef25a955e189e3f38", size = 289754, upload-time = "2026-05-11T19:52:48.232Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1b/df94ad1cb79705d717f7e1063da642c538a6d7ce6443c8e60355fa507ea4/backports_zstd-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e7c0372fa036751109604c70a8c87e59faaacc195d519c8cb9e0e527ee2b5478", size = 314829, upload-time = "2026-05-11T19:52:50.031Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/24e60da7cc89b9ed1c5b474678e316dd0ddfe7cd1de39b23d04452ca5946/backports_zstd-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:264a66137555bb4648f7e64cfc514d820758072684f373269fcdd2e8d4a90306", size = 291497, upload-time = "2026-05-11T19:52:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/24/71/29ed213344f8f62b7520745d7df3752d88db456aff9d8b706bdf5eb99a3c/backports_zstd-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1858cacdb3e50105a1b60acdc3dd5b18650077d12dce243e19d5c88e8172bd71", size = 437170, upload-time = "2026-05-11T19:52:53.204Z" }, + { url = "https://files.pythonhosted.org/packages/d0/e3/a58a3eb8fc54d4e3e4f684ed7b1f688da02e5bda5ae5e2809e94cf2ead2f/backports_zstd-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ccffc0a1974ecc2cc42afa4c15f56d036a4b2bae0abc46e6ba9b3358d9b1c037", size = 363265, upload-time = "2026-05-11T19:52:55.153Z" }, + { url = "https://files.pythonhosted.org/packages/3f/03/9d13840d206dec1c4698c803f61c58379b3578cb9dc6140ba5fa4ce2f31d/backports_zstd-1.5.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:ab3430ab4d4ac3fb1bc1e4174d137731e51363b6abd5e51a1599690fe9c7d61d", size = 507527, upload-time = "2026-05-11T19:52:57.256Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8f/8dc4b5736dca218cbca9609549a8f6dc202990abdb49afdc6112442f5360/backports_zstd-1.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c737c1cb4a10c2d0f6cba9a347522858094f0a737b4558c67a777bcaa4a795cd", size = 477352, upload-time = "2026-05-11T19:52:59.425Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/65a66976a761b5b62eacbaed5ed418c694b24b5c480399315d799751de62/backports_zstd-1.5.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0379c66510681a6b2780d3f3ef2cff54d01204b52448d64bde1855d40f856a04", size = 582799, upload-time = "2026-05-11T19:53:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/ee93a66cd28cb3ad7f3c04d1105325a5428671b18bd41ba9ed8b43bc44cf/backports_zstd-1.5.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c7474b291e264c9609358d3875cf539623f7a65339c2b533020992b1a4c095b", size = 641530, upload-time = "2026-05-11T19:53:03.082Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4b/2cecd4d6679f175f28ae02022bd2050ff4023e38902fae104dbe2e231911/backports_zstd-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb73c22444617bc5a3abf32dd27b3f2085898cfe3b95e6855300e9189898a3bd", size = 495324, upload-time = "2026-05-11T19:53:05.005Z" }, + { url = "https://files.pythonhosted.org/packages/4d/20/ee21e4e791e31f38f7a70b3961eb64b350d9be802a335e7a04c02b41b197/backports_zstd-1.5.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6cd7f6c33afd89354f74469e315e72754e3040f91f7b685061e225d9e36e3e8e", size = 569796, upload-time = "2026-05-11T19:53:07.011Z" }, + { url = "https://files.pythonhosted.org/packages/76/da/86c9a2ea384885b60638b3e47113198449568d0e36ef3834d1f969623092/backports_zstd-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2106309071f279b38d3663c55c7fed192733b4f332b50eb3fa707e54bad6967a", size = 483367, upload-time = "2026-05-11T19:53:08.674Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f0/c95c6e4dd28fc314547782a482839e422283d62c2aaf45d30672109a4a1e/backports_zstd-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:56fffa80be74cb11ac843333bbdc56e466c87967706886b3efd6b16d83830d90", size = 510976, upload-time = "2026-05-11T19:53:10.339Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a2/72777b7e1872228a13b09b0bf77ae6cf626008d462cc2e1a0ae64721fd55/backports_zstd-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5e8b8251eec80e67e30ec79dfc5b3b1ada069b9ac48b56b102f3e2c6f8281062", size = 587190, upload-time = "2026-05-11T19:53:12.205Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/db5d1aee59da308eadeaa189764a4ec68e98495c309a13dcb8da5718fef1/backports_zstd-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f334dd17ffead361aa9090e40151bd123507ce213a62733121b7145c6711cbde", size = 567395, upload-time = "2026-05-11T19:53:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/00/0f/39ca1a6e8c5c2dc81da9e06c44d1990cc464f4b16dae214e877afd7adfc0/backports_zstd-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:78cbfd061255fef6de5070a54e0f9c00e8aabad5c99dd2ad884a3a7d1acc09ae", size = 632048, upload-time = "2026-05-11T19:53:16.234Z" }, + { url = "https://files.pythonhosted.org/packages/73/fd/a438ee4fc615016dbe96112b709b6805ee19eb215f46e208c8fbce086d8d/backports_zstd-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f55d70df44f49d599e20033013bc1ae705202735c45d4bca8eb963b225e15fd", size = 499833, upload-time = "2026-05-11T19:53:17.85Z" }, + { url = "https://files.pythonhosted.org/packages/f7/42/f544fde4de32687e28c514288ae3c11106ba644e9dd580992cbd704bbb49/backports_zstd-1.5.0-cp312-cp312-win32.whl", hash = "sha256:a8b096e0383a3bcab34f8c97b79e1a52051189d11258bbc2bc1145997a15dd1d", size = 289876, upload-time = "2026-05-11T19:53:19.486Z" }, + { url = "https://files.pythonhosted.org/packages/ad/31/9c29cd3175892e5ee909f5e8d14707fa07815301ff24b5c697d1cea62a77/backports_zstd-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e2802899ba4ef1a062ffe4bb1292c5df32011a54b4c3004c54f46ec975f39554", size = 314933, upload-time = "2026-05-11T19:53:20.942Z" }, + { url = "https://files.pythonhosted.org/packages/11/ee/1a50acd6446c0d57c4f93ad6ce68e1a631ad920737a6b2d0bbbc47de7f42/backports_zstd-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:3c0353e66942afbd45518788cfbd1e9e117828ceb390fa50517f46f291850d8e", size = 291665, upload-time = "2026-05-11T19:53:22.686Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e6/252521e3a847eb200bc0a1d528542d651b9c8dc7953e231c39ed2890d5ff/backports_zstd-1.5.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:02a57ee8598dd863c0b11c7af00042ce6bc045bf6f4249fa4c322c62614ca1fd", size = 400134, upload-time = "2026-05-11T19:53:24.28Z" }, + { url = "https://files.pythonhosted.org/packages/36/43/27ef105ffa2da3d52218d4a7b2e14037974283953b3ee790358af6e9b4df/backports_zstd-1.5.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:c56c11eb3173d540e1fb0216f7ab477cbd3a204eca41f5f329059ee8a5d2ad47", size = 454225, upload-time = "2026-05-11T19:53:25.874Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/cdcba1244347500d00567ce2cd6bf04c92d1b0fb6405fb8e13c07715eb46/backports_zstd-1.5.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ef98f632026aa8e6ce05d786977092798efbe78677aa71219f22d31787809c90", size = 357229, upload-time = "2026-05-11T19:53:27.661Z" }, + { url = "https://files.pythonhosted.org/packages/df/da/cea04dab3ffb940bde9a59866bde6f2594a7b3ef2948a63fb3898f73d311/backports_zstd-1.5.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:c3712300b18f9d07f788b03594b2f34dfad89d77df96938a640c5007522a6b69", size = 365907, upload-time = "2026-05-11T19:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/da/c4/6a71df2e65033f9b7d8017d77ea2bb572fc2ebc814ea383fdcda4187597a/backports_zstd-1.5.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:bdbc75d1f54df70b65bcfbc8aa0cac21475f79665bb045960af606dc07b56090", size = 446453, upload-time = "2026-05-11T19:53:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/66/e7/f98ad1a6a249c27884df9d28cf6ebc3c368e0e3288a741c1d51a572bb3d7/backports_zstd-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93d306300d25e59f1cbe98cda494bf295be03a20e8b2c5602ee5ddc03ded29f2", size = 436634, upload-time = "2026-05-11T19:53:32.484Z" }, + { url = "https://files.pythonhosted.org/packages/ba/42/d0393ecc64e2ab6ae1b5ca7edbe26e3fe5196885f15d6cc4bce7254e29cd/backports_zstd-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:305d2e4ae9a595d0fd9d5bea5a7a2163306c6c4dcc5eec35ecd5008219d4580e", size = 362867, upload-time = "2026-05-11T19:53:34.385Z" }, + { url = "https://files.pythonhosted.org/packages/41/fe/87aa9404763bada695d06e5cb9d0575bae033cbf3a2e4e3bd648760178f7/backports_zstd-1.5.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:c8f0967bf8d806b250fb1e905a6b8190e7ae83656d5308989243f84e01fa3774", size = 506844, upload-time = "2026-05-11T19:53:36.023Z" }, + { url = "https://files.pythonhosted.org/packages/56/94/3af7ce637d148e0b0acb1298b61afe9a934ed425bad9ff05e87afbf6766d/backports_zstd-1.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76b7314ca9a253171e3e9524960e9e6411997323cf10aecbbc330faa7a90278d", size = 476975, upload-time = "2026-05-11T19:53:37.885Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6c/dc2aa1b48296ac6effc3bacb5a3061d40ed74bf73082dfe38eed2ba8362b/backports_zstd-1.5.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b1d0bf16bba86b1071731ced389f184e8de61c1afcafa584244f7f726632f92f", size = 582496, upload-time = "2026-05-11T19:53:39.812Z" }, + { url = "https://files.pythonhosted.org/packages/f6/38/dd49d3dd27eda9b165ccd63d70538fea016a3e9e42923bbbc1d89fae8a43/backports_zstd-1.5.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:96709d27d406008575ef759405169d538040156704b457d8c0ac035127a46b67", size = 643257, upload-time = "2026-05-11T19:53:41.819Z" }, + { url = "https://files.pythonhosted.org/packages/59/75/78e819272450aec2462f97a1bceb90bde481f9dba435bf9e76d580b4dec4/backports_zstd-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5737402c29b2bd5bc661d4cde08aed531ed326f2b59a7ad98dc07650dc99a2c9", size = 491958, upload-time = "2026-05-11T19:53:43.501Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/d860f9cf21cb59d583a12166353bf71a439538e2b669f4a7736e400ca596/backports_zstd-1.5.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b65f37ddd375114dbf84658e7dd168e10f5a93394940bfefa7fafc2d3234450", size = 567198, upload-time = "2026-05-11T19:53:45.226Z" }, + { url = "https://files.pythonhosted.org/packages/38/7c/b175d4c9ff60f964c8f6dd43211de905227cfde5a41eb5f654df58483025/backports_zstd-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fae7825dde4f81c28b4c66b1e997f893e296c3f1668351952b3ed085eb9f8cd", size = 482792, upload-time = "2026-05-11T19:53:47.323Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e3/f7b50cf891a10da5f9c412ed4a9c4a772df4d4186d98a41e75c9b462f148/backports_zstd-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3aa10e77c0e712d2dfb950910b50591c2fb11f0f1328814e23acc0b4950766df", size = 510363, upload-time = "2026-05-11T19:53:49.523Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/e7841fd4a65661d527697a0e2dab97295868965ccd4e3e12474472719a60/backports_zstd-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:518b2ef54ce0fee6d29379cfd64ef66e639456f1b18943466e929b19677f135f", size = 586917, upload-time = "2026-05-11T19:53:51.741Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7c/57e985dbd621f0307b8c57cabb258eb976793f2aeaf8a5bc020e15b4a793/backports_zstd-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:673a1e5fdaa6cb0c7a967eb33066b6dd564871b3498a93e11e2972998047d11f", size = 565004, upload-time = "2026-05-11T19:53:53.774Z" }, + { url = "https://files.pythonhosted.org/packages/2f/8f/855ffcd1ee0fcf44c3fe62e36db8e7362292d450cc7c4b3f43edccbcd37a/backports_zstd-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1277c07ff2d731586aa05aebd946a1b30184620d886a735dd5d5bf94a4a1061e", size = 633737, upload-time = "2026-05-11T19:53:56.036Z" }, + { url = "https://files.pythonhosted.org/packages/20/39/c4129a03d268699200dfebe1ccab97c7c332d2794571afb372a62e4ed098/backports_zstd-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aff334c7c38b4aea2a899f3138a99c1d58f0686ad7815c74bff506ecf4333296", size = 496309, upload-time = "2026-05-11T19:53:57.591Z" }, + { url = "https://files.pythonhosted.org/packages/8e/33/34152316dd244dcd43d5300ded3cf6e1b46d343e4e92620c23e533fa91df/backports_zstd-1.5.0-cp313-cp313-win32.whl", hash = "sha256:b932834c4d85360f46d1e7fbf3eee1e26ba594e0eb5c3ee1281e89bc1d48d06f", size = 289560, upload-time = "2026-05-11T19:53:59.274Z" }, + { url = "https://files.pythonhosted.org/packages/71/c5/f759bc87fd77c88f4fdad2d878535fb7e9537c6a05876d206e6690bf33c6/backports_zstd-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:c71dfbeced720326a8917a6edf921c568dc2396228c6432205c6d7e7fe7f3707", size = 314812, upload-time = "2026-05-11T19:54:00.909Z" }, + { url = "https://files.pythonhosted.org/packages/47/96/d7970dbb2fef34b549b34146090f48f41903cc7268b1ed1c7542eaa1852e/backports_zstd-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:7b5798b20ffff71ee4620a01f56fe0b50271724b4251db08c90a069446cc4752", size = 291411, upload-time = "2026-05-11T19:54:02.541Z" }, + { url = "https://files.pythonhosted.org/packages/5b/35/294ce0d818455191ee9a0f21d987d6061d4f844ca34ca44a8b1daaaba3ca/backports_zstd-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9685586eb67fa2e59eab8027d48e8275ce90e404b6dc737b508f741853ba6cb7", size = 410912, upload-time = "2026-05-11T19:54:04.031Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5c/99fba38e6d57cf238362d4ac568823b1fb75e20f75b58cd062a3da4d9a7a/backports_zstd-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a68ab446d007d34e12f5a812e6f7d1c120a3d15cb5d4e62b7568926a6da6fb7", size = 340429, upload-time = "2026-05-11T19:54:05.632Z" }, + { url = "https://files.pythonhosted.org/packages/e1/bc/146fdb7b0bf39817e1b706e34be46f2cf11d5465668e1912747dd45fd71b/backports_zstd-1.5.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:627973d4375a42500a66cc2ea912f6223249a6cdfeb56cc340b0d20b5a3475d0", size = 421477, upload-time = "2026-05-11T19:54:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2e/6e43d94a3414d0113439c5e9ae6b04311797cfef5d04dc1d3aa0bcbff057/backports_zstd-1.5.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c077639e99de02a679dca9c6a189f60a76e7d0096977c0ebd070c31de8df57a", size = 395021, upload-time = "2026-05-11T19:54:09.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/41/d599f31e5152f43397f837c6911bffee8626d6d079bcaafab04d1a8a07ad/backports_zstd-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ac2b3895fc9b1f0b0e71bffa179b48930dc27643b7e4885869afd295e7dfe1e", size = 414986, upload-time = "2026-05-11T19:54:10.986Z" }, + { url = "https://files.pythonhosted.org/packages/26/62/006a63d5a13a04384b9cd35e35f78944a75c975f5a71c25e81cc766d53d7/backports_zstd-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41b23cbd72f503aedcaaaa23d55d2d98d449e5938154d2b3f57832c73b286cee", size = 300853, upload-time = "2026-05-11T19:54:12.593Z" }, + { url = "https://files.pythonhosted.org/packages/89/92/8e8769e1e3ebec16d39f455e317a0f137a191b1f122853d0377c660666ce/backports_zstd-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0ca2d4ac4901eada2cfb86fda692e5d4a1e09485d9f2ec5777dc6cd3154b3b46", size = 410809, upload-time = "2026-05-11T19:54:14.117Z" }, + { url = "https://files.pythonhosted.org/packages/63/5c/741a2923020c45b85cad4dffffcb86dbfa2d4aaed27f18ee793428ef4c24/backports_zstd-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:20796211a623ec6e0061cef4d7cca760e9e0a0a951bb30dc9ba89ed4a3fea5e4", size = 340342, upload-time = "2026-05-11T19:54:16.165Z" }, + { url = "https://files.pythonhosted.org/packages/c8/3b/68c4fe8a551d3f47ed75ddcf15dc7c777bb9d869fc0e0f5b7cacc9f158f5/backports_zstd-1.5.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:5232cd2a58c60da4ceb0e09e42dbc579b92dda4a9301a756af0c738223a23487", size = 421476, upload-time = "2026-05-11T19:54:17.709Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4d/ab5dcd6ab9a7ac02ec42c4507211da7dadb9498abb655115c296077e2b8b/backports_zstd-1.5.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:012d88a9ae08f331e1adc03dfbda4ff2ae7f76ea62455975827b215677a11aec", size = 395020, upload-time = "2026-05-11T19:54:19.566Z" }, + { url = "https://files.pythonhosted.org/packages/55/aa/ec512a0d14552bbb4e75693f7065434b865956abd045ceb67f0574146241/backports_zstd-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb7d79f8e43b6e0e17616961e425b9f8b32d9933e1db69242baa6e21f44a978", size = 414985, upload-time = "2026-05-11T19:54:21.136Z" }, + { url = "https://files.pythonhosted.org/packages/aa/31/759d077aa680555e17c9d2bb09edf4c3428d895fe5d35a8df67684401b84/backports_zstd-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6172dcdd664ef243e55a35e6b45f1c866767c61043f0ddcd908abd14df662065", size = 300853, upload-time = "2026-05-11T19:54:23.1Z" }, +] + +[[package]] +name = "bc-detect-secrets" +version = "1.4.30" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, + { name = "unidiff" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/d5/a9686feec143958737b0bb7f713c32760ce46414bc375271f996a09e5139/bc-detect-secrets-1.4.30.tar.gz", hash = "sha256:53154e5aee4c3415f20d5be327c9dad240804b113196ec6346c0b5390a7ed52d", size = 85205, upload-time = "2023-08-03T07:06:52.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/8b/07956de4f78d35c224b92fc33987628e1777da67e4d7749051cf286486e3/bc_detect_secrets-1.4.30-py3-none-any.whl", hash = "sha256:99fd6a7e5033ea3a4532d3ccc11b3f669c447da0109e0cb9d66998f4131dba0c", size = 118262, upload-time = "2023-08-03T07:06:51.609Z" }, +] + +[[package]] +name = "bc-jsonpath-ng" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, + { name = "ply" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/ad/b6745e21e050fac1ea499fdcafb689391ebf2ff01f2a96da275bb189c2ed/bc-jsonpath-ng-1.6.1.tar.gz", hash = "sha256:6ea4e379c4400a511d07605b8d981950292dd098a5619d143328af4e841a2320", size = 36478, upload-time = "2023-11-26T13:29:31.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/88/27b4b4374e96bfd6b8e49cdde4e5aaa61eb9046b8ead9b18dd2d3ad6a154/bc_jsonpath_ng-1.6.1-py3-none-any.whl", hash = "sha256:2c85bb1d194376808fe1fc49558dd484e39024b15c719995e22de811e6ba4dc8", size = 29783, upload-time = "2023-11-26T13:29:28.789Z" }, +] + +[[package]] +name = "bc-python-hcl2" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/9a/1cb1e4c65a75dcc304438d1f941345d5dd5e8819f1a8b7b17991a21a22b3/bc-python-hcl2-0.4.2.tar.gz", hash = "sha256:ac8ff59fb9bd437ea29b89a7d7c507fd0a1e957845bae9aeac69f2892b8d681e", size = 12314, upload-time = "2023-12-11T13:01:45.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/87/9773bc69f78fa40d1a8b29fe9b9e4ad1254fa3198479a51833ad1407d538/bc_python_hcl2-0.4.2-py3-none-any.whl", hash = "sha256:90d2afbaa2c7e77b7b30bf58180084e11d95287f7c3e19c5bfbdb54ab2fd80e9", size = 14917, upload-time = "2023-12-11T13:01:43.2Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "boltons" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/1f/6c0608d86e0fc77c982a2923ece80eef85f091f2332fc13cbce41d70d502/boltons-21.0.0.tar.gz", hash = "sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13", size = 180201, upload-time = "2021-05-17T01:20:17.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/a7/1a31561d10a089fcb46fe286766dd4e053a12f6e23b4fd1c26478aff2475/boltons-21.0.0-py2.py3-none-any.whl", hash = "sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b", size = 193723, upload-time = "2021-05-17T01:20:20.023Z" }, +] + +[[package]] +name = "boolean-py" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/2f/c4159fa45079b41f11ad17d8c5df8e1d10169b94d1e4240df5be116d3f0a/boto3-1.43.12.tar.gz", hash = "sha256:4a60cdf02c52cb0a60f8dbc986142ce2c31e87e3df1438ffe6755b83008f3e4e", size = 113142, upload-time = "2026-05-20T19:38:13.163Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/35/b7ab4b6977811f9887405e24460640033c22f4515cf1e904480710bd6296/boto3-1.43.12-py3-none-any.whl", hash = "sha256:685c3e6093455623bfc22dac55b4946ea243095252f7f9c11a99d84b38033bcf", size = 140537, upload-time = "2026-05-20T19:38:09.995Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/95ec376c2300e605225998619c46f7093c515710b9d6d65f891f126f32b6/botocore-1.43.12.tar.gz", hash = "sha256:7608ecd51687132e22aa8b82acb89a5917b1b68ec0563c25d82c3e16adab9bc0", size = 15366431, upload-time = "2026-05-20T19:37:58.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/20/b2ef618de8dc634361e32344bdc5139f1ad92968ab6c18cddd6c8c431f67/botocore-1.43.12-py3-none-any.whl", hash = "sha256:75dfb84c6edbb5aaa0314d93776d840d74e26e8d97e0431270a3274d70abeba3", size = 15046449, upload-time = "2026-05-20T19:37:53.723Z" }, +] + +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + +[[package]] +name = "cachecontrol" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z" }, +] + +[package.optional-dependencies] +filecache = [ + { name = "filelock" }, +] + +[[package]] +name = "cached-property" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/4b/3d870836119dbe9a5e3c9a61af8cc1a8b69d75aea564572e385882d5aefb/cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641", size = 10574, upload-time = "2024-10-25T15:43:55.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/0e/7d8225aab3bc1a0f5811f8e1b557aa034ac04bdf641925b30d3caf586b28/cached_property-2.0.1-py3-none-any.whl", hash = "sha256:f617d70ab1100b7bcf6e42228f9ddcb78c676ffa167278d9f730d1c2fba69ccb", size = 7428, upload-time = "2024-10-25T15:43:54.711Z" }, +] + +[[package]] +name = "cachetools" +version = "7.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/c1/67cfb86aa21144796ff51068326d467fbef8ee42f8d08a3a8a926106cf0c/cachetools-7.1.3.tar.gz", hash = "sha256:135cfe944bc3c1e805505f65dae0bef375a2f96261171ab66c79ef77d0bda39d", size = 45780, upload-time = "2026-05-18T18:21:03.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, - { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, - { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, + { url = "https://files.pythonhosted.org/packages/68/52/8ff5c1a3b2e821ced9b2998fba3ee29aa4525c0bf51e5ee55dd6f61a4ed5/cachetools-7.1.3-py3-none-any.whl", hash = "sha256:9876787e2346e20584d5cca236cb5d49d04e7193de91646f230725b2e1e8b804", size = 16763, upload-time = "2026-05-18T18:21:02.386Z" }, ] [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] -name = "cfgv" -version = "3.5.0" +name = "cffi" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] @@ -230,16 +688,106 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] +[[package]] +name = "checkov" +version = "3.1.47" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiodns" }, + { name = "aiohttp" }, + { name = "aiomultiprocess" }, + { name = "argcomplete" }, + { name = "bc-detect-secrets" }, + { name = "bc-jsonpath-ng" }, + { name = "bc-python-hcl2" }, + { name = "boto3" }, + { name = "cachetools" }, + { name = "charset-normalizer" }, + { name = "click" }, + { name = "cloudsplaining" }, + { name = "colorama" }, + { name = "configargparse" }, + { name = "cyclonedx-python-lib" }, + { name = "docker" }, + { name = "dockerfile-parse" }, + { name = "dpath" }, + { name = "gitpython" }, + { name = "igraph" }, + { name = "importlib-metadata" }, + { name = "jmespath" }, + { name = "jsonschema" }, + { name = "junit-xml" }, + { name = "license-expression" }, + { name = "networkx" }, + { name = "openai" }, + { name = "packageurl-python" }, + { name = "packaging" }, + { name = "prettytable" }, + { name = "pycep-parser" }, + { name = "pydantic" }, + { name = "pyston", marker = "(python_full_version < '3.11' and implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "pyston-autoload", marker = "(python_full_version < '3.11' and implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rustworkx" }, + { name = "schema" }, + { name = "semantic-version" }, + { name = "spdx-tools" }, + { name = "tabulate" }, + { name = "termcolor" }, + { name = "tqdm" }, + { name = "typing-extensions" }, + { name = "update-checker" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/4a/4c5fe8eb890dd386eec7694cc60c5cc11fe5d089f1544708b700b40b7fc4/checkov-3.1.47.tar.gz", hash = "sha256:c7d412f6ae6fadf0508b74c92057db7541c7f6305db702f8f3e90f4e8c848670", size = 859212, upload-time = "2023-12-31T08:14:20.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/ed/64103e71511c2be10dad3ba595fa713dbd5796b32b556a7eaf8a8e12f0e8/checkov-3.1.47-py3-none-any.whl", hash = "sha256:8b7648964d04e919cb1935baf65f460f778eba0668d20e8619d136596548ca1b", size = 1970052, upload-time = "2023-12-31T08:13:56.15Z" }, +] + [[package]] name = "click" -version = "8.3.3" +version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click-option-group" +version = "0.5.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/ff/d291d66595b30b83d1cb9e314b2c9be7cfc7327d4a0d40a15da2416ea97b/click_option_group-0.5.9.tar.gz", hash = "sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823", size = 22222, upload-time = "2025-10-09T09:38:01.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/45/54bb2d8d4138964a94bef6e9afe48b0be4705ba66ac442ae7d8a8dc4ffef/click_option_group-0.5.9-py3-none-any.whl", hash = "sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080", size = 11553, upload-time = "2025-10-09T09:38:00.066Z" }, +] + +[[package]] +name = "cloudsplaining" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "cached-property" }, + { name = "click" }, + { name = "click-option-group" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "policy-sentry" }, + { name = "pyyaml" }, + { name = "schema" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/64/ba28b9b1854a40bcaae318da0e0fe147bd25999e496a8382a5a67c463db1/cloudsplaining-0.8.2.tar.gz", hash = "sha256:733085a7648e45714a24e629d05d3dfd592d2925b21fe001c19f55a6d6c1581a", size = 1746329, upload-time = "2025-10-10T21:29:42.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, + { url = "https://files.pythonhosted.org/packages/12/ee/7c9a2c4fa97be65931005cdbea1e293a8eedf1df28c825aedae3fa510cc0/cloudsplaining-0.8.2-py3-none-any.whl", hash = "sha256:e9c098622db99fa71dc8de5475a4f3db1bb442a782f720199afc232eb418fb47", size = 1799493, upload-time = "2025-10-10T21:29:39.986Z" }, ] [[package]] @@ -251,6 +799,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "commonmark" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/48/a60f593447e8f0894ebb7f6e6c1f25dafc5e89c5879fdc9360ae93ff83f0/commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", size = 95764, upload-time = "2019-10-04T15:37:39.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/92/dfd892312d822f36c55366118b95d914e5f16de11044a27cf10a7d71bbbf/commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9", size = 51068, upload-time = "2019-10-04T15:37:37.674Z" }, +] + +[[package]] +name = "configargparse" +version = "1.7.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/0b/30328302903c55218ffc5199646d0e9d28348ff26c02ba77b2ffc58d294a/configargparse-1.7.5.tar.gz", hash = "sha256:e3f9a7bb6be34d66b2e3c4a2f58e3045f8dfae47b0dc039f87bcfaa0f193fb0f", size = 53548, upload-time = "2026-03-11T02:19:38.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/19/3ba5e1b0bcc7b91aeab6c258afd70e4907d220fed3972febe38feb40db30/configargparse-1.7.5-py3-none-any.whl", hash = "sha256:1e63fdffedf94da9cd435fc13a1cd24777e76879dd2343912c1f871d4ac8c592", size = 27692, upload-time = "2026-03-11T02:19:36.442Z" }, +] + [[package]] name = "coverage" version = "7.14.0" @@ -370,141 +936,537 @@ toml = [ ] [[package]] -name = "csscompressor" -version = "0.9.5" +name = "cryptography" +version = "48.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/8c3ac3d8bc94e6de8d7ae270bb5bc437b210bb9d6d9e46630c98f4abd20c/csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05", size = 237808, upload-time = "2017-11-26T21:13:08.238Z" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, + { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, + { url = "https://files.pythonhosted.org/packages/be/d2/024b5e06be9d44cb021fb0e1a03d34d63989cf56a0fe62f3dfbab695b9b4/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855", size = 3950391, upload-time = "2026-05-04T22:59:17.415Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" }, +] [[package]] -name = "distlib" -version = "0.4.0" +name = "cyclonedx-python-lib" +version = "5.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +dependencies = [ + { name = "license-expression" }, + { name = "packageurl-python" }, + { name = "py-serializable" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/9e/5198201845fa810ad0645401ae3e25f1ce15ebc2c0c40df003c320e5e189/cyclonedx_python_lib-5.2.0.tar.gz", hash = "sha256:b9ebf2c0520721d2f8ee16aadc2bbb9d4e015862c84ab1691a49b177f3014d99", size = 433613, upload-time = "2023-12-02T11:27:49.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/47/7f/06dc2d41b079c6d7773007349ee86acdc9557c164bf3fe6ee0977e5a0e6b/cyclonedx_python_lib-5.2.0-py3-none-any.whl", hash = "sha256:1b43065205cdc53490c825fcfbda73142b758aa40ca169c968e342e88d06734f", size = 187664, upload-time = "2023-12-02T11:27:47.298Z" }, ] [[package]] -name = "distro" -version = "1.9.0" +name = "decorator" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ] [[package]] -name = "exceptiongroup" -version = "1.3.1" +name = "deepmerge" +version = "2.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, ] [[package]] -name = "filelock" -version = "3.29.0" +name = "defusedxml" +version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] -name = "ghp-import" -version = "2.1.0" +name = "detect-secrets" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/67/382a863fff94eae5a0cf05542179169a1c49a4c8784a9480621e2066ca7d/detect_secrets-1.5.0.tar.gz", hash = "sha256:6bb46dcc553c10df51475641bb30fd69d25645cc12339e46c824c1e0c388898a", size = 97351, upload-time = "2024-05-06T17:46:19.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/4f5fe4b89fde1dc3ed0eb51bd4ce4c0bca406246673d370ea2ad0c58d747/detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060", size = 120341, upload-time = "2024-05-06T17:46:16.628Z" }, ] [[package]] -name = "griffelib" -version = "2.0.2" +name = "distlib" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] -name = "h11" -version = "0.16.0" +name = "distro" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] [[package]] -name = "hjson" -version = "3.1.0" +name = "docker" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, ] [[package]] -name = "htmlmin2" -version = "0.1.13" +name = "dockerfile-parse" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/df/929ee0b5d2c8bd8d713c45e71b94ab57c7e11e322130724d54f469b2cd48/dockerfile-parse-2.0.1.tar.gz", hash = "sha256:3184ccdc513221983e503ac00e1aa504a2aa8f84e5de673c46b0b6eee99ec7bc", size = 24556, upload-time = "2023-07-18T13:36:07.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/31/a76f4bfa885f93b8167cb4c85cf32b54d1f64384d0b897d45bc6d19b7b45/htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2", size = 34486, upload-time = "2023-03-14T21:28:30.388Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6c/79cd5bc1b880d8c1a9a5550aa8dacd57353fa3bb2457227e1fb47383eb49/dockerfile_parse-2.0.1-py2.py3-none-any.whl", hash = "sha256:bdffd126d2eb26acf1066acb54cb2e336682e1d72b974a40894fac76a4df17f6", size = 14845, upload-time = "2023-07-18T13:36:06.052Z" }, ] [[package]] -name = "httpcore" -version = "1.0.9" +name = "dpath" +version = "2.1.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/2c/a4213cdbbc43b8fdf34b6e2afb415fd5d46e171d32a4bb92e7924548aa9f/dpath-2.1.3.tar.gz", hash = "sha256:d1a7a0e6427d0a4156c792c82caf1f0109603f68ace792e36ca4596fd2cb8d9d", size = 24016, upload-time = "2022-12-13T07:27:22.108Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/12/02fdb87afeab1987442164a2db470a995122c800f15265e9b7a5103a3fd9/dpath-2.1.3-py3-none-any.whl", hash = "sha256:d9560e03ccd83b3c6f29988b0162ce9b34fd28b9d8dbda46663b20c68d9cdae3", size = 17232, upload-time = "2022-12-13T07:27:20.023Z" }, ] [[package]] -name = "httpx" -version = "0.28.1" +name = "exceptiongroup" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] [[package]] -name = "identify" -version = "2.6.19" +name = "execnet" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] [[package]] -name = "idna" -version = "3.14" +name = "face" +version = "26.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" } +dependencies = [ + { name = "boltons" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/4e/0e106b0ba486cc38c858fb5efe899002f2ec4765e0808b298d8e19a16efb/face-26.0.0.tar.gz", hash = "sha256:ae12136ff0052f124811f5319670a8d9d29b7d2caaaabe542813690967cc6bca", size = 49862, upload-time = "2026-02-14T00:17:12.576Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" }, + { url = "https://files.pythonhosted.org/packages/63/1d/c2f7a4334f7501a3474766b5bc0948e8e0b0916217a54d092dd700a5ed3c/face-26.0.0-py3-none-any.whl", hash = "sha256:6ec9cf271d8ee2447f04b14264209a09ec9cbe8252255e61fb7ab6b154e300f9", size = 54825, upload-time = "2026-02-14T00:17:11.519Z" }, +] + +[[package]] +name = "fake-useragent" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898, upload-time = "2025-04-14T15:32:19.238Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/354ae6491228b5eb40e10d89c4d13c651fe1cf7556e35ebdded50cff57ce/gitpython-3.1.50.tar.gz", hash = "sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc", size = 219798, upload-time = "2026-05-06T04:01:26.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" }, +] + +[[package]] +name = "glom" +version = "25.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "boltons" }, + { name = "face" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/74/8387f95565ba7c30cd152a585b275ebb9a834d1d32782425c5d2fe0a102c/glom-25.12.0.tar.gz", hash = "sha256:1ae7da88be3693df40ad27bdf57a765a55c075c86c971bcddd67927403eb0069", size = 196128, upload-time = "2025-12-29T06:29:07.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/e6/4129d9a3baa72d747533bb33376543ccadd9a7f9944e5a6e3ae2e245f5d6/glom-25.12.0-py3-none-any.whl", hash = "sha256:b9f21e77f71a6576a43864e85066b8cc3f0f778d0d50961563f8981377a6dcb1", size = 103295, upload-time = "2025-12-29T06:29:06.074Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hatch" +version = "1.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-zstd", marker = "python_full_version < '3.14'" }, + { name = "click" }, + { name = "hatchling" }, + { name = "httpx" }, + { name = "hyperlink" }, + { name = "keyring" }, + { name = "packaging" }, + { name = "pexpect" }, + { name = "platformdirs" }, + { name = "pyproject-hooks" }, + { name = "python-discovery" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "tomli-w" }, + { name = "tomlkit" }, + { name = "userpath" }, + { name = "uv" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/02/ce9c4c439fa3f195b21b4b5bb18b44d1076297c86477ef7e3d2de6064ec3/hatch-1.16.5.tar.gz", hash = "sha256:57bdeeaa72577859ce37091a5449583875331c06f9cb6af9077947ad40b3a1de", size = 5220741, upload-time = "2026-02-27T18:45:31.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/8a/11ae7e271870f0ad8fa0012e4265982bebe0fdc21766b161fb8b8fc3aefc/hatch-1.16.5-py3-none-any.whl", hash = "sha256:d9b8047f2cd10d3349eb6e8f278ad728a04f91495aace305c257d5c2747188fb", size = 141269, upload-time = "2026-02-27T18:45:29.573Z" }, +] + +[[package]] +name = "hatchling" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "hyperlink" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/51/1947bd81d75af87e3bb9e34593a4cf118115a8feb451ce7a69044ef1412e/hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", size = 140743, upload-time = "2021-01-08T05:51:20.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4", size = 74638, upload-time = "2021-01-08T05:51:22.906Z" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "igraph" +version = "0.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "texttable" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/f8/542bcfceddee678799baf858d5ee4788a59028eb555ac7b35315f6ffaad5/igraph-0.10.8.tar.gz", hash = "sha256:d3b7893573060d117917e4f2121e524ed849bbf9f9a63a082001e1a4c5225b46", size = 4239123, upload-time = "2023-09-12T20:05:05.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/7c/7d7f008f04d1d1d75a5e5e5ad92b26748ffe3efd68385dd5719f3b4c1668/igraph-0.10.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7ee4e9cb183a92524f336399b548d2540f5150ab7ce6a270618709efa9e04c58", size = 1933707, upload-time = "2023-09-12T20:03:58.629Z" }, + { url = "https://files.pythonhosted.org/packages/38/d8/0abcc30c61d60fdd460677b3ca1d6ac16f4a657d50eea8f461da76396e27/igraph-0.10.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:42fb6b69de069d0dc66a8606ad486aae19b500b01268cb1e64877ea931bd7ad7", size = 1695476, upload-time = "2023-09-12T20:04:01.098Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a6/5eef0404d689cd3e7ff6c030b2d9ccca96a9dfe00e48c4e14ce281d8b45b/igraph-0.10.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03f5ab062e2caa092da901266258193963cc38690964e4c75b5ef2b457b86f35", size = 3141634, upload-time = "2023-09-12T20:04:04.481Z" }, + { url = "https://files.pythonhosted.org/packages/7b/28/956443884d89cd09287d29839a513cf3303bf955f69d2a1b8e4f650b5aff/igraph-0.10.8-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00ae729d928d2218822a1f1f07d1286918456f9d587691a4c1c743b2206dadd6", size = 3296146, upload-time = "2023-09-12T20:04:06.977Z" }, + { url = "https://files.pythonhosted.org/packages/b3/70/c488485bcb439b9ccf0bc117bd9c8634bc36f66fa8fda2fc01fb406542e7/igraph-0.10.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89fca31ca33957dae5df4ecf032c08eb1e897cbc5856cdaad52b67c6ace3f074", size = 3288934, upload-time = "2023-09-12T20:04:09.146Z" }, + { url = "https://files.pythonhosted.org/packages/15/c2/910d6aae4e4bdbdd5f86ed0da31e4679222b093fc9c6def831e26fd6d53e/igraph-0.10.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d7bd3455c93486b2f443f6f177792a358540207334b66dc73681d63972e1f1d9", size = 3591304, upload-time = "2023-09-12T20:04:10.974Z" }, + { url = "https://files.pythonhosted.org/packages/aa/78/1b922af44ca351d22752bede90ee7eba152284f86277f8e45310523cb983/igraph-0.10.8-cp39-abi3-musllinux_1_1_i686.whl", hash = "sha256:15927b565094bd5d3ec3a89ce4abd6087c3d07bf03dd6d506dd05f2678de963d", size = 3742714, upload-time = "2023-09-12T20:04:13.021Z" }, + { url = "https://files.pythonhosted.org/packages/19/f3/e534aac344318e0f8f0a17cc36cc8c66d7ff72906a0aa608516b1fda67e6/igraph-0.10.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e5c692fce053ba79b9d4b82b40e092b83a2bd14e04a09871c8e4a68c39932010", size = 3716214, upload-time = "2023-09-12T20:04:15.323Z" }, + { url = "https://files.pythonhosted.org/packages/80/6b/3907bae34e716d45614a77772f8adf5f42135f646fc9c934d0dce38acb21/igraph-0.10.8-cp39-abi3-win32.whl", hash = "sha256:3cc8349311d9ffe225f752e093cebf5d21929f1bfa7281e510248706b6516199", size = 2537480, upload-time = "2023-09-12T20:04:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/15db01b0343fa0833e0994bd4aa70ccb88cfb1c045f1cbfb0938fa81ef27/igraph-0.10.8-cp39-abi3-win_amd64.whl", hash = "sha256:88ee0d0ab83481f365ef4e56f9f9e9f70001d90ebd6ed98e368060494481d022", size = 2894676, upload-time = "2023-09-12T20:04:19.156Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/1fde28490e59eb5bdfff93e9590e67df51b2a2ee0558f62eeb4973e7d56d/igraph-0.10.8-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:922c85e2c6a29c09b8fadac3c92e0e38eecaaff48b5c569b82cf006409c4ba4a", size = 1917313, upload-time = "2023-09-12T20:04:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a3/9ecf729a57b1d8d56d08c9eac56d2efa566adae53175fc784380cb355072/igraph-0.10.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ce856489ae76ebc286535a64833d141f7ea43a106ba3485cd7ea9bbd47871c", size = 2760319, upload-time = "2023-09-12T20:04:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6b/fb91c6d4b6557a4ac6f18d9631aa2545f6b13a015483c16d47058a92fda9/igraph-0.10.8-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e5d00251082fe7e4044aa58b153b7f6250dbbff49b97f3c7f2400f31f456456", size = 2996746, upload-time = "2023-09-12T20:04:25.816Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9c/5f7d48cbf6c0400abb1a3414cfee8e557600391840f06bbb497c49082d9d/igraph-0.10.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f3e29f89ae9ce5c60ef09a0a5d3ea41aac6bf54cce0110f4e8dfcc782eb4f90", size = 2914578, upload-time = "2023-09-12T20:04:27.584Z" }, + { url = "https://files.pythonhosted.org/packages/f2/68/eea6fb91555204e84e0d61b8d22ad6e05252b5ee3e659ee4306c1772c142/igraph-0.10.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27c47695fe37f4cecbbe84c56326c6aed1ef876a62838c995cc7f7dd6fc8d408", size = 2898212, upload-time = "2023-09-12T20:04:30.221Z" }, ] [[package]] @@ -528,6 +1490,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -541,94 +1557,90 @@ wheels = [ ] [[package]] -name = "jsmin" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/73/e01e4c5e11ad0494f4407a3f623ad4d87714909f50b17a06ed121034ff6e/jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc", size = 13925, upload-time = "2022-01-16T20:35:59.13Z" } - -[[package]] -name = "librt" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/10/37fd9e9ba96cb0bd742dfb20fc3d082e54bdbec759d7300df927f360ef07/librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", size = 141706, upload-time = "2026-05-10T18:15:16.129Z" }, - { url = "https://files.pythonhosted.org/packages/cf/72/1b1466f358e4a0b728051f69bc27e67b432c6eaa2e05b88db49d3785ae0d/librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", size = 142605, upload-time = "2026-05-10T18:15:18.148Z" }, - { url = "https://files.pythonhosted.org/packages/ca/85/ed26dd2f6bc9a0baf48306433e579e8d354d70b2bcb78134ed950a5d0e1e/librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", size = 476555, upload-time = "2026-05-10T18:15:19.569Z" }, - { url = "https://files.pythonhosted.org/packages/66/fe/11891191c0e0a3fd617724e891f6e67a71a7658974a892b9a9a97fdb2977/librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", size = 468434, upload-time = "2026-05-10T18:15:20.87Z" }, - { url = "https://files.pythonhosted.org/packages/6f/50/5ec949d7f9ce1a07af903aa3e13abb98b717923bdead6e719b2f824ccc07/librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", size = 496918, upload-time = "2026-05-10T18:15:22.616Z" }, - { url = "https://files.pythonhosted.org/packages/ea/c4/177336c7524e34875a38bf668e88b193a6723a4eb4045d07f74df6e1506c/librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", size = 490334, upload-time = "2026-05-10T18:15:24.2Z" }, - { url = "https://files.pythonhosted.org/packages/13/1f/da3112f7569eda3b49f9a2629bae1fe059812b6085df16c885f6454dff49/librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", size = 511287, upload-time = "2026-05-10T18:15:26.226Z" }, - { url = "https://files.pythonhosted.org/packages/fa/94/03fec301522e172d105581431223be56b27594ff46440ebfbb658a3735d5/librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", size = 517202, upload-time = "2026-05-10T18:15:27.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/6e/339f6e5a7b413ce014f1917a756dae630fe59cc99f34153205b1cb540901/librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", size = 497517, upload-time = "2026-05-10T18:15:29.614Z" }, - { url = "https://files.pythonhosted.org/packages/cd/43/acdd5ce317cb46e8253ca9bfbdb8b12e68a24d745949336a7f3d5fb79ba0/librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", size = 538878, upload-time = "2026-05-10T18:15:30.928Z" }, - { url = "https://files.pythonhosted.org/packages/29/b5/7a25bb12e3172839f647f196b3e988318b7bb1ca7501732a225c4dce2ec0/librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", size = 100070, upload-time = "2026-05-10T18:15:32.551Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0d/ebbcf4d77999c02c937b05d2b90ff4cd4dcc7e9a365ba132329ac1fe7a0f/librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", size = 117918, upload-time = "2026-05-10T18:15:33.678Z" }, - { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, - { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, - { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, - { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, - { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, - { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, - { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, - { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, - { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, - { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, - { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, - { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, - { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, - { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, - { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, - { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, - { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, - { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, - { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, - { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, - { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, - { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, - { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, - { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, - { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, - { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, - { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, - { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, - { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, - { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, - { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, - { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, - { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, - { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "junit-xml" +version = "1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/af/bc988c914dd1ea2bc7540ecc6a0265c2b6faccc6d9cdb82f20e2094a8229/junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f", size = 7349, upload-time = "2023-01-24T18:42:00.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/93/2d896b5fd3d79b4cadd8882c06650e66d003f465c9d12c488d92853dff78/junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732", size = 7130, upload-time = "2020-02-22T20:41:37.661Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, +] + +[[package]] +name = "license-expression" +version = "30.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boolean-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402, upload-time = "2025-07-22T11:13:32.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z" }, ] [[package]] @@ -766,6 +1778,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "mcp" +version = "1.23.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" }, +] + [[package]] name = "mdformat" version = "1.0.0" @@ -779,6 +1816,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/9a/8fe71b95985ca7a4001effbcc58e5a07a1f2a2884203f74dcf48a3b08315/mdformat-1.0.0-py3-none-any.whl", hash = "sha256:bca015d65a1d063a02e885a91daee303057bc7829c2cd37b2075a50dbb65944b", size = 53288, upload-time = "2025-10-16T12:05:02.607Z" }, ] +[[package]] +name = "mdformat-footnote" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdformat" }, + { name = "mdit-py-plugins" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/c0/0fe461e3c53eb72b35b642add6c3eaf1f90e43a574a18633955f9e89791d/mdformat_footnote-0.1.3.tar.gz", hash = "sha256:70617e61af87f59d7dea93a392c5093089a6d4551126fa18025c6fddb18bf742", size = 6430, upload-time = "2026-01-30T14:13:05.983Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/ac/60d47237a0e37fe991cbf311f10f096a28cfdbbe25d1932dadf8e9abdac8/mdformat_footnote-0.1.3-py3-none-any.whl", hash = "sha256:3033184237cbaf2a71cc02a4c37f043a97c1f16d3ed6939bc104964e77aad0b3", size = 7767, upload-time = "2026-01-30T14:13:04.944Z" }, +] + [[package]] name = "mdformat-frontmatter" version = "2.0.10" @@ -808,16 +1858,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/18/6bc2189b744dd383cad03764f41f30352b1278d2205096f77a29c0b327ad/mdformat_gfm-1.0.0-py3-none-any.whl", hash = "sha256:7305a50efd2a140d7c83505b58e3ac5df2b09e293f9bbe72f6c7bee8c678b005", size = 10970, upload-time = "2025-10-16T09:12:21.276Z" }, ] +[[package]] +name = "mdformat-gfm-alerts" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdformat" }, + { name = "mdit-py-plugins" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/33/70619cf9e2b9e35857ebcf7e947e8f5370df2ff2a4ab666895783f8fab6c/mdformat_gfm_alerts-2.0.0.tar.gz", hash = "sha256:eb2b3189ad44ae28a6b6b714609dd3a30d6e3b898f02b1e6473d7b08df8bb3c0", size = 9687, upload-time = "2025-06-05T20:49:46.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ea/22e093bb2ef3e25dc3ccc9d8392c14b069a007ae394a12e058d9e1f51c00/mdformat_gfm_alerts-2.0.0-py3-none-any.whl", hash = "sha256:e003422cc003bc6e936d0797553f23201095a1d1e8602c5062296d223f2ae516", size = 6752, upload-time = "2025-06-05T20:49:45.539Z" }, +] + [[package]] name = "mdit-py-plugins" -version = "0.6.0" +version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/3d/e0e8d9d1cee04f758120915e2b2a3a07eb41f8cf4654b4734788a522bcd1/mdit_py_plugins-0.6.0.tar.gz", hash = "sha256:2436f14a7295837ac9228a36feeabda867c4abc488c8d019ad5c0bda88eee040", size = 56025, upload-time = "2026-05-07T12:20:42.295Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl", hash = "sha256:f7e7a25d8b616fee99cb1e330da73451d11a8061baf39bb9663ab9ce0e005b90", size = 66655, upload-time = "2026-05-07T12:20:41.226Z" }, + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, ] [[package]] @@ -838,23 +1901,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] -[[package]] -name = "mike" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "mkdocs" }, - { name = "pyparsing" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "verspec" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/47/fa87e9d56bef16cdfe34b059a437e8c6f7ec6f1b9c378871c3cf95ebea9c/mike-2.2.0.tar.gz", hash = "sha256:1e3858e32c0f125aac14432fc7848434358f9ae0962c5c5cde387ad47f6ad25e", size = 38450, upload-time = "2026-04-14T04:59:03.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl", hash = "sha256:e1f4981c1152eec7c2490a3401142292cc47d686194188416db2648fdfe1d040", size = 34026, upload-time = "2026-04-14T04:59:02.602Z" }, -] - [[package]] name = "mkdocs" version = "1.6.1" @@ -879,20 +1925,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] -[[package]] -name = "mkdocs-autorefs" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, -] - [[package]] name = "mkdocs-gen-files" version = "0.6.1" @@ -921,346 +1953,1087 @@ wheels = [ ] [[package]] -name = "mkdocs-literate-nav" -version = "0.6.3" +name = "monotable" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/f1/30e60c602de5f98a1fa3dd2e515dc4d025ee9e410b0106c43a51bb26ca3d/monotable-3.2.0.tar.gz", hash = "sha256:3e215bdac7d0849d7e4f79c67790009f1ce63814a06403bef229c347cfbe3bc6", size = 91356, upload-time = "2024-09-14T15:49:22.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/30/30e4ce8130ea9645f8f56dff676ce2aeb68d9d0b5f556904145d3ea18a0e/monotable-3.2.0-py3-none-any.whl", hash = "sha256:5b870a8bb02ca3717f554535f8ce5a0a62b2ad99adc237a363144d5874251bde", size = 46450, upload-time = "2024-09-14T15:49:20.528Z" }, +] + +[[package]] +name = "more-itertools" +version = "11.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, +] + +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/a2/3b68a9e769db68668b25c6108444a35f9bd163bb848c0650d516761a59c0/msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2", size = 81318, upload-time = "2025-10-08T09:14:38.722Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/2b720cc341325c00be44e1ed59e7cfeae2678329fbf5aa68f5bda57fe728/msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87", size = 83786, upload-time = "2025-10-08T09:14:40.082Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/c2241de64bfceac456b140737812a2ab310b10538a7b34a1d393b748e095/msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251", size = 398240, upload-time = "2025-10-08T09:14:41.151Z" }, + { url = "https://files.pythonhosted.org/packages/b7/09/2a06956383c0fdebaef5aa9246e2356776f12ea6f2a44bd1368abf0e46c4/msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a", size = 406070, upload-time = "2025-10-08T09:14:42.821Z" }, + { url = "https://files.pythonhosted.org/packages/0e/74/2957703f0e1ef20637d6aead4fbb314330c26f39aa046b348c7edcf6ca6b/msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f", size = 393403, upload-time = "2025-10-08T09:14:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/a5/09/3bfc12aa90f77b37322fc33e7a8a7c29ba7c8edeadfa27664451801b9860/msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f", size = 398947, upload-time = "2025-10-08T09:14:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4f/05fcebd3b4977cb3d840f7ef6b77c51f8582086de5e642f3fefee35c86fc/msgpack-1.1.2-cp310-cp310-win32.whl", hash = "sha256:e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9", size = 64769, upload-time = "2025-10-08T09:14:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3e/b4547e3a34210956382eed1c85935fff7e0f9b98be3106b3745d7dec9c5e/msgpack-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa", size = 71293, upload-time = "2025-10-08T09:14:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, + { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" }, + { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, + { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" }, + { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" }, + { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" }, + { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" }, + { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs" }, - { name = "properdocs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/af/dd3776a7a713f798f79bec7eb9c661d5cfb83ddc17d9a3667595e53e1559/mkdocs_literate_nav-0.6.3.tar.gz", hash = "sha256:edbaca22343f861fe4e34aac47d55a0c9955c640dbf02eea99fe631e914cf9ee", size = 17526, upload-time = "2026-03-16T23:26:50.688Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/2c/bcf1ae903975ad6f169abb05c1eb0f94395478364deb89270cf034081b29/mkdocs_literate_nav-0.6.3-py3-none-any.whl", hash = "sha256:2c421561280fa9184f88cbf399bebbd4cc17ee507e978a31ce11fd6f3aabf233", size = 13355, upload-time = "2026-03-16T23:26:49.562Z" }, + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] [[package]] -name = "mkdocs-macros-plugin" -version = "1.5.0" +name = "networkx" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/ae/7497bc5e1c84af95e585e3f98585c9f06c627fac6340984c4243053e8f44/networkx-2.6.3.tar.gz", hash = "sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51", size = 1844862, upload-time = "2021-09-09T22:09:42.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/93/aa6613aa70d6eb4868e667068b5a11feca9645498fd31b954b6c4bb82fa5/networkx-2.6.3-py3-none-any.whl", hash = "sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef", size = 1927288, upload-time = "2021-09-09T22:09:39.016Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.11' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "openai" +version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "hjson" }, - { name = "jinja2" }, - { name = "mkdocs" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, + { name = "aiohttp" }, { name = "requests" }, - { name = "super-collections" }, - { name = "termcolor" }, + { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/15/e6a44839841ebc9c5872fa0e6fad1c3757424e4fe026093b68e9f386d136/mkdocs_macros_plugin-1.5.0.tar.gz", hash = "sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f", size = 37730, upload-time = "2025-11-13T08:08:55.545Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/fe/c21d95cc120928b0f5b44d8c522e48df122be3f1f9d61dfb7bf3d597c95d/openai-0.28.1.tar.gz", hash = "sha256:4be1dad329a65b4ce1a660fe6d5431b438f429b5855c883435f0f7fcb6d2dcc8", size = 61939, upload-time = "2023-09-26T03:36:14.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl", hash = "sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f", size = 44626, upload-time = "2025-11-13T08:08:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9f/385c25502f437686e4aa715969e5eaf5c2cb5e5ffa7c5cdd52f3c6ae967a/openai-0.28.1-py3-none-any.whl", hash = "sha256:d18690f9e3d31eedb66b57b88c2165d760b24ea0a01f150dd3f068155088ce68", size = 76950, upload-time = "2023-09-26T03:36:09.98Z" }, ] [[package]] -name = "mkdocs-material" -version = "9.7.6" +name = "opentelemetry-api" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "babel" }, - { name = "backrefs" }, - { name = "colorama" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "mkdocs" }, - { name = "mkdocs-material-extensions" }, - { name = "paginate" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923, upload-time = "2025-09-11T10:29:01.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6c/10018cbcc1e6fff23aac67d7fd977c3d692dbe5f9ef9bb4db5c1268726cc/opentelemetry_exporter_otlp_proto_common-1.37.0.tar.gz", hash = "sha256:c87a1bdd9f41fdc408d9cc9367bb53f8d2602829659f2b90be9f9d79d0bfe62c", size = 20430, upload-time = "2025-09-11T10:29:03.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/13/b4ef09837409a777f3c0af2a5b4ba9b7af34872bc43609dda0c209e4060d/opentelemetry_exporter_otlp_proto_common-1.37.0-py3-none-any.whl", hash = "sha256:53038428449c559b0c564b8d718df3314da387109c4d36bd1b94c9a641b0292e", size = 18359, upload-time = "2025-09-11T10:28:44.939Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, { name = "requests" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/29/6d2bcf41ae40802c4beda2432396fff97b8456fb496371d1bc7aad6512ec/mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69", size = 4097959, upload-time = "2026-03-19T15:41:58.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/e3/6e320aeb24f951449e73867e53c55542bebbaf24faeee7623ef677d66736/opentelemetry_exporter_otlp_proto_http-1.37.0.tar.gz", hash = "sha256:e52e8600f1720d6de298419a802108a8f5afa63c96809ff83becb03f874e44ac", size = 17281, upload-time = "2025-09-11T10:29:04.844Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba", size = 9305470, upload-time = "2026-03-19T15:41:55.217Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/70d74a664d83976556cec395d6bfedd9b85ec1498b778367d5f93e373397/opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef", size = 19576, upload-time = "2025-09-11T10:28:46.726Z" }, ] [[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" +name = "opentelemetry-instrumentation" +version = "0.58b0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/36/7c307d9be8ce4ee7beb86d7f1d31027f2a6a89228240405a858d6e4d64f9/opentelemetry_instrumentation-0.58b0.tar.gz", hash = "sha256:df640f3ac715a3e05af145c18f527f4422c6ab6c467e40bd24d2ad75a00cb705", size = 31549, upload-time = "2025-09-11T11:42:14.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, + { url = "https://files.pythonhosted.org/packages/d4/db/5ff1cd6c5ca1d12ecf1b73be16fbb2a8af2114ee46d4b0e6d4b23f4f4db7/opentelemetry_instrumentation-0.58b0-py3-none-any.whl", hash = "sha256:50f97ac03100676c9f7fc28197f8240c7290ca1baa12da8bfbb9a1de4f34cc45", size = 33019, upload-time = "2025-09-11T11:41:00.624Z" }, ] [[package]] -name = "mkdocs-minify-plugin" -version = "0.8.0" +name = "opentelemetry-instrumentation-requests" +version = "0.58b0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "csscompressor" }, - { name = "htmlmin2" }, - { name = "jsmin" }, - { name = "mkdocs" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/67/fe4b77e7a8ae7628392e28b14122588beaf6078b53eb91c7ed000fd158ac/mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d", size = 8366, upload-time = "2024-01-29T16:11:32.982Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/42/83ee32de763b919779aaa595b60c5a7b9c0a4b33952bbe432c5f6a783085/opentelemetry_instrumentation_requests-0.58b0.tar.gz", hash = "sha256:ae9495e6ff64e27bdb839fce91dbb4be56e325139828e8005f875baf41951a2e", size = 15188, upload-time = "2025-09-11T11:42:51.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/cd/2e8d0d92421916e2ea4ff97f10a544a9bd5588eb747556701c983581df13/mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6", size = 6723, upload-time = "2024-01-29T16:11:31.851Z" }, + { url = "https://files.pythonhosted.org/packages/90/4d/f3476b28ea167d1762134352d01ae9693940a42c78994d9f1b32a4477816/opentelemetry_instrumentation_requests-0.58b0-py3-none-any.whl", hash = "sha256:672a0be0bb5b52bea0c11820b35e27edcf4cd22d34abe4afc59a92a80519f8a8", size = 12966, upload-time = "2025-09-11T11:41:52.67Z" }, ] [[package]] -name = "mkdocs-nav-weight" -version = "0.3.0" +name = "opentelemetry-instrumentation-threading" +version = "0.58b0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/08/1b996d23571758287ee4098e16779d2b44b58005dc2b80f20423774d7ce5/mkdocs_nav_weight-0.3.0.tar.gz", hash = "sha256:e4b66f0693aa1d8b82876727f3c5a6aa3c85834069dfd1b5ea23432ff3d862bb", size = 11329, upload-time = "2025-09-09T11:44:33.288Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/a9/3888cb0470e6eb48ea17b6802275ae71df411edd6382b9a8e8f391936fda/opentelemetry_instrumentation_threading-0.58b0.tar.gz", hash = "sha256:f68c61f77841f9ff6270176f4d496c10addbceacd782af434d705f83e4504862", size = 8770, upload-time = "2025-09-11T11:42:56.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/ec/12153ca0b5821b8a0a443431da7427df21a3566a96410e744b7cb62465c9/mkdocs_nav_weight-0.3.0-py3-none-any.whl", hash = "sha256:f95b2f2ca3f3125e95c4518ed66df4a1b9082d37840b352a5a3e3c2559276cba", size = 6910, upload-time = "2025-09-09T11:44:32.025Z" }, + { url = "https://files.pythonhosted.org/packages/a5/54/add1076cb37980e617723a96e29c84006983e8ad6fc589dde7f69ddc57d4/opentelemetry_instrumentation_threading-0.58b0-py3-none-any.whl", hash = "sha256:eacc072881006aceb5b9b6831bcdce718c67ef6f31ac0b32bd6a23a94d979b4a", size = 9312, upload-time = "2025-09-11T11:41:58.603Z" }, ] [[package]] -name = "mkdocs-section-index" -version = "0.3.12" +name = "opentelemetry-proto" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs" }, - { name = "properdocs" }, + { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/e2/64d0f3f054ca8efe61e706006ff5f0d49ad99620c62c2e04818573391c33/mkdocs_section_index-0.3.12.tar.gz", hash = "sha256:285635bf86c643b0fc7a343053d7a818049817bff4408f52b80c4367bd5e7268", size = 14946, upload-time = "2026-04-16T19:20:00.953Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/ea/a75f36b463a36f3c5a10c0b5292c58b31dbdde74f6f905d3d0ab2313987b/opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538", size = 46151, upload-time = "2025-09-11T10:29:11.04Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/4d/a330cab5e055d45e924cec69da54a3d8ed37643964f8d1fa1a772b496273/mkdocs_section_index-0.3.12-py3-none-any.whl", hash = "sha256:a1100039546beb4ebef63ce6fc91f3195fb9c0c3763105d4d3d7cd31e0a046eb", size = 8932, upload-time = "2026-04-16T19:19:59.741Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/f89ea66c59bd7687e218361826c969443c4fa15dfe89733f3bf1e2a9e971/opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2", size = 72534, upload-time = "2025-09-11T10:28:56.831Z" }, ] [[package]] -name = "mkdocstrings" -version = "1.0.4" +name = "opentelemetry-sdk" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, - { name = "mkdocs-autorefs" }, - { name = "pymdown-extensions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/5d/f888d4d3eb31359b327bc9b17a212d6ef03fe0b0682fbb3fc2cb849fb12b/mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172", size = 100088, upload-time = "2026-04-15T09:16:53.376Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/62/2e0ca80d7fe94f0b193135375da92c640d15fe81f636658d2acf373086bc/opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5", size = 170404, upload-time = "2025-09-11T10:29:11.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b", size = 35560, upload-time = "2026-04-15T09:16:51.436Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/9f4ad6a54126fb00f7ed4bb5034964c6e4f00fcd5a905e115bd22707e20d/opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c", size = 131941, upload-time = "2025-09-11T10:28:57.83Z" }, ] -[package.optional-dependencies] -python = [ - { name = "mkdocstrings-python" }, +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/1b/90701d91e6300d9f2fb352153fb1721ed99ed1f6ea14fa992c756016e63a/opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25", size = 129867, upload-time = "2025-09-11T10:29:12.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/90/68152b7465f50285d3ce2481b3aec2f82822e3f52e5152eeeaf516bab841/opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28", size = 207954, upload-time = "2025-09-11T10:28:59.218Z" }, ] [[package]] -name = "mkdocstrings-python" -version = "2.0.3" +name = "opentelemetry-util-http" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5f/02f31530faf50ef8a41ab34901c05cbbf8e9d76963ba2fb852b0b4065f4e/opentelemetry_util_http-0.58b0.tar.gz", hash = "sha256:de0154896c3472c6599311c83e0ecee856c4da1b17808d39fdc5cce5312e4d89", size = 9411, upload-time = "2025-09-11T11:43:05.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/a3/0a1430c42c6d34d8372a16c104e7408028f0c30270d8f3eb6cccf2e82934/opentelemetry_util_http-0.58b0-py3-none-any.whl", hash = "sha256:6c6b86762ed43025fbd593dc5f700ba0aa3e09711aedc36fd48a13b23d8cb1e7", size = 7652, upload-time = "2025-09-11T11:42:09.682Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, + { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, + { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, + { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, + { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffelib" }, - { name = "mkdocs-autorefs" }, - { name = "mkdocstrings" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, ] [[package]] -name = "mypy" -version = "2.1.0" +name = "packageurl-python" +version = "0.17.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/d6/3b5a4e3cfaef7a53869a26ceb034d1ff5e5c27c814ce77260a96d50ab7bb/packageurl_python-0.17.6.tar.gz", hash = "sha256:1252ce3a102372ca6f86eb968e16f9014c4ba511c5c37d95a7f023e2ca6e5c25", size = 50618, upload-time = "2025-11-24T15:20:17.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/2f/c7277b7615a93f51b5fbc1eacfc1b75e8103370e786fd8ce2abf6e5c04ab/packageurl_python-0.17.6-py3-none-any.whl", hash = "sha256:31a85c2717bc41dd818f3c62908685ff9eebcb68588213745b14a6ee9e7df7c9", size = 36776, upload-time = "2025-11-24T15:20:16.962Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "peewee" +version = "3.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/b0/79462b42e89764998756e0557f2b58a15610a5b4512fbbcccae58fba7237/peewee-3.19.0.tar.gz", hash = "sha256:f88292a6f0d7b906cb26bca9c8599b8f4d8920ebd36124400d0cbaaaf915511f", size = 974035, upload-time = "2026-01-07T17:24:59.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/41/19c65578ef9a54b3083253c68a607f099642747168fe00f3a2bceb7c3a34/peewee-3.19.0-py3-none-any.whl", hash = "sha256:de220b94766e6008c466e00ce4ba5299b9a832117d9eb36d45d0062f3cfd7417", size = 411885, upload-time = "2026-01-07T17:24:58.33Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ast-serialize" }, - { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "phmdoctest" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "commonmark" }, + { name = "monotable" }, + { name = "pytest" }, { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/71/d351dca3e9b30da2328ee9d445c88b8388072808ebfbc49eb69d30b67749/mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", size = 14778792, upload-time = "2026-05-11T18:36:23.605Z" }, - { url = "https://files.pythonhosted.org/packages/2f/45/7d51594b644c17c0bcf74ed8cd5fc33b324276d708e8506f220b70dab9d9/mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", size = 13645739, upload-time = "2026-05-11T18:37:22.752Z" }, - { url = "https://files.pythonhosted.org/packages/65/01/455c31b170e9468265074840bf18863a8482a24103fdaabe4e199392aa5f/mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", size = 14074199, upload-time = "2026-05-11T18:35:09.292Z" }, - { url = "https://files.pythonhosted.org/packages/41/5a/93093f0b29a9e982deafde698f740a2eb2e05886e79ccf0594c7fd5413a3/mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", size = 14953128, upload-time = "2026-05-11T18:31:57.678Z" }, - { url = "https://files.pythonhosted.org/packages/7f/2f/a196f5331d96170ad3d28f144d2aba690d4b2911381f68d51e489c7ab82a/mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", size = 15249378, upload-time = "2026-05-11T18:33:00.101Z" }, - { url = "https://files.pythonhosted.org/packages/54/de/94d321cc12da9f71341ac0c270efbed5c725750c7b4c334d957de9a087d9/mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", size = 11060994, upload-time = "2026-05-11T18:33:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/e1/62/0c27ca55219a7c764a7fb88c7bb2b7b2f9780ade8bbf16bc8ed8400eef6b/mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", size = 9976743, upload-time = "2026-05-11T18:31:25.554Z" }, - { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, - { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, - { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, - { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, - { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, - { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, - { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, - { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, - { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, - { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, - { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, - { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, - { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, - { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, - { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, - { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, - { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, - { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, - { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, - { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, - { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" +sdist = { url = "https://files.pythonhosted.org/packages/98/86/4e967c119158e7a0d4974346a0076d529e34be44ee182c35e3a98c3ea26d/phmdoctest-1.4.0.tar.gz", hash = "sha256:eb9dd5dc415d6e48ccd3a273e250c519b1e2b9e22e21ddf6e6f716efbb0f2e09", size = 113039, upload-time = "2022-03-19T19:10:27.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/dd/0700cb1dda98c2677bb79d7330ff2190bd19793dc55721fbae09822838fb/phmdoctest-1.4.0-py3-none-any.whl", hash = "sha256:4c9db90314183bc874d20947dba4ee93018d0412380f26137db0050a8b7903a9", size = 43589, upload-time = "2022-03-19T19:10:25.471Z" }, +] + +[[package]] +name = "pip" +version = "26.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/48/cb9b7a682f6fe01a4221e1728941dd4ac3cd9090a17db3779d6ff490b602/pip-26.1.1.tar.gz", hash = "sha256:d36762751d156a4ee895de8af39aa0abeeeb577f93a2eca6ab62467bbf0f8a78", size = 1840400, upload-time = "2026-05-04T19:02:21.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl", hash = "sha256:99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb", size = 1812777, upload-time = "2026-05-04T19:02:18.9Z" }, ] [[package]] -name = "nodeenv" -version = "1.10.0" +name = "pip-api" +version = "0.0.34" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +dependencies = [ + { name = "pip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/f1/ee85f8c7e82bccf90a3c7aad22863cc6e20057860a1361083cd2adacb92e/pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625", size = 123017, upload-time = "2024-07-09T20:32:30.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/91/f7/ebf5003e1065fd00b4cbef53bf0a65c3d3e1b599b676d5383ccb7a8b88ba/pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb", size = 120369, upload-time = "2024-07-09T20:32:29.099Z" }, ] [[package]] -name = "opentelemetry-api" -version = "1.41.1" +name = "pip-audit" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, - { name = "typing-extensions" }, + { name = "cachecontrol", extra = ["filecache"] }, + { name = "cyclonedx-python-lib" }, + { name = "packaging" }, + { name = "pip-api" }, + { name = "pip-requirements-parser" }, + { name = "platformdirs" }, + { name = "requests" }, + { name = "rich" }, + { name = "tomli" }, + { name = "tomli-w" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/fc/b7564cbef36601aef0d6c9bc01f7badb64be8e862c2e1c3c5c3b43b53e4f/opentelemetry_api-1.41.1.tar.gz", hash = "sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621", size = 71416, upload-time = "2026-04-24T13:15:38.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/89/0e999b413facab81c33d118f3ac3739fd02c0622ccf7c4e82e37cebd8447/pip_audit-2.10.0.tar.gz", hash = "sha256:427ea5bf61d1d06b98b1ae29b7feacc00288a2eced52c9c58ceed5253ef6c2a4", size = 53776, upload-time = "2025-12-01T23:42:40.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/59/3e7118ed140f76b0982ba4321bdaed1997a0473f9720de2d10788a577033/opentelemetry_api-1.41.1-py3-none-any.whl", hash = "sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f", size = 69007, upload-time = "2026-04-24T13:15:15.662Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/4888f895c02afa085630a3a3329d1b18b998874642ad4c530e9a4d7851fe/pip_audit-2.10.0-py3-none-any.whl", hash = "sha256:16e02093872fac97580303f0848fa3ad64f7ecf600736ea7835a2b24de49613f", size = 61518, upload-time = "2025-12-01T23:42:39.193Z" }, ] [[package]] -name = "opentelemetry-sdk" -version = "1.41.1" +name = "pip-requirements-parser" +version = "32.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "typing-extensions" }, + { name = "packaging" }, + { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/d0/54ee30dab82fb0acda23d144502771ff76ef8728459c83c3e89ef9fb1825/opentelemetry_sdk-1.41.1.tar.gz", hash = "sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6", size = 230180, upload-time = "2026-04-24T13:15:50.991Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/2a/63b574101850e7f7b306ddbdb02cb294380d37948140eecd468fae392b54/pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3", size = 209359, upload-time = "2022-12-21T15:25:22.732Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/e7/a1420b698aad018e1cf60fdbaaccbe49021fb415e2a0d81c242f4c518f54/opentelemetry_sdk-1.41.1-py3-none-any.whl", hash = "sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d", size = 180213, upload-time = "2026-04-24T13:15:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/54/d0/d04f1d1e064ac901439699ee097f58688caadea42498ec9c4b4ad2ef84ab/pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526", size = 35648, upload-time = "2022-12-21T15:25:21.046Z" }, ] [[package]] -name = "opentelemetry-semantic-conventions" -version = "0.62b1" +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "ply" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, +] + +[[package]] +name = "policy-sentry" +version = "0.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api" }, - { name = "typing-extensions" }, + { name = "beautifulsoup4" }, + { name = "click" }, + { name = "orjson" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "schema" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/88/2ae4e253775a6b20766e49d9f4552a73d559d0815cf4d8789f3144241c66/policy_sentry-0.14.2.tar.gz", hash = "sha256:a6c3d86a449cb4a40c149f2a0241fe0101da594e42f63a20a895f1322a07b708", size = 1077724, upload-time = "2025-12-02T07:09:15.112Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/89/559abc0001ac8e942447e8ac762403a6dc2b3c48d1762307c6778d25e043/policy_sentry-0.14.2-py3-none-any.whl", hash = "sha256:5271b78f7debb130d28380043e3d181f9fbe0d29f792ecf962434ef21121758e", size = 1077187, upload-time = "2025-12-02T07:09:13.38Z" }, +] + +[[package]] +name = "prettytable" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/45/b0847d88d6cfeb4413566738c8bbf1e1995fad3d42515327ff32cc1eb578/prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0", size = 67892, upload-time = "2025-11-14T17:33:20.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433, upload-time = "2025-11-14T17:33:19.093Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/56/030b7b4719d53085722893e0009dffb9236aa10bca1b12121bdc5626ef16/propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b", size = 93417, upload-time = "2026-05-08T20:59:15.597Z" }, + { url = "https://files.pythonhosted.org/packages/1a/55/1140a8e067b8ec093a18a4ae7bb0045d9db65da38a08618ddc5e2f1994aa/propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c", size = 53847, upload-time = "2026-05-08T20:59:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/0e7443c90310498561addf346e7d57fe3c6ba1914e1ba938b5464c7bbfd2/propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb", size = 53512, upload-time = "2026-05-08T20:59:18.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/db/cf51a71bab2009517d1a7f0ee07657e3bd446c4d69f67e6966cf17bcf956/propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e", size = 58068, upload-time = "2026-05-08T20:59:20.683Z" }, + { url = "https://files.pythonhosted.org/packages/b7/43/39b6bdee9699fa1e1641c519feeb64a67e2a9f93bb465c70776b37a7333f/propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e", size = 61020, upload-time = "2026-05-08T20:59:22.112Z" }, + { url = "https://files.pythonhosted.org/packages/26/0b/843726fbb0a29a8c5684fdb25971823638399f31e52e9d1f06a02dc9aa6b/propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b", size = 62732, upload-time = "2026-05-08T20:59:23.805Z" }, + { url = "https://files.pythonhosted.org/packages/39/6e/899fed76dc1942b8a64193a4f059d7f1a2c7ef65085e8a9366ed8ec0d199/propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d", size = 60140, upload-time = "2026-05-08T20:59:25.389Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/3da4be9b5b879219ad234aa535b3dd4a080ed1ad48d3a73ca07a9e798f22/propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d", size = 60400, upload-time = "2026-05-08T20:59:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/60/2f/09b72b874a9aa0044faf52a69807a6ed618e267ceaa9ec4a63195fa5b504/propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0", size = 58155, upload-time = "2026-05-08T20:59:28.48Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/97489848c54c95578045473954f10956d619ce6a09e7ac137b71cdcb698b/propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b", size = 57037, upload-time = "2026-05-08T20:59:30.146Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/6c695285ccfc49012743ee9c98212b8c5dd0aed7b63cfd816d4a0f7a1601/propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf", size = 61103, upload-time = "2026-05-08T20:59:31.626Z" }, + { url = "https://files.pythonhosted.org/packages/98/a9/1e500401ca593b0bdb6bf75a70bc2d723835fd53360edff6af70692c7546/propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf", size = 60394, upload-time = "2026-05-08T20:59:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/f638b6e375eae0f30a1a2325d8b34fd85fdc785bb9960cf805f3bf1ec69a/propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e", size = 63084, upload-time = "2026-05-08T20:59:35.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/18/884573f5d97b6d9eba68de759a82c901b7e39d7904d30f7b8d58d42d2a12/propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274", size = 60999, upload-time = "2026-05-08T20:59:38.481Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/c3915eb059ceec9e758a56e4cfd955292bc0f201be2176a46b76d94b303a/propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe", size = 39036, upload-time = "2026-05-08T20:59:40.323Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/1dfd5607501a602d19c1c449d2d193b7d1c611f9246b4059026a1189a80e/propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d", size = 42190, upload-time = "2026-05-08T20:59:42.232Z" }, + { url = "https://files.pythonhosted.org/packages/57/93/f71588ad08b3e6f4b555b5ef215808a3c02b042d0151ad82fa6f15be677a/propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5", size = 38545, upload-time = "2026-05-08T20:59:44.087Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "properdocs" +version = "1.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/de/911ac9e309052aca1b20b2d5549d3db45d1011e1a610e552c6ccdd1b64f8/opentelemetry_semantic_conventions-0.62b1.tar.gz", hash = "sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802", size = 145750, upload-time = "2026-04-24T13:15:52.236Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/29/f27a4e1eddf72ed3db6e47818fbafe6debbf09fd7051f9c1a007239b46ef/properdocs-1.6.7.tar.gz", hash = "sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e", size = 276141, upload-time = "2026-03-20T20:07:48.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/a6/83dc2ab6fa397ee66fba04fe2e74bdf7be3b3870005359ceb7689103c058/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl", hash = "sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c", size = 231620, upload-time = "2026-04-24T13:15:35.454Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4d/fc923f5c85318ee8cc903566dc4e0ebe41b2dfc1d2ecf5546db232397ed6/properdocs-1.6.7-py3-none-any.whl", hash = "sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd", size = 225406, upload-time = "2026-03-20T20:07:46.875Z" }, ] [[package]] -name = "packaging" -version = "26.2" +name = "protobuf" +version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] -name = "paginate" -version = "0.5.7" +name = "ptyprocess" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, ] [[package]] -name = "pathspec" -version = "1.1.1" +name = "py-serializable" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +dependencies = [ + { name = "defusedxml" }, ] - -[[package]] -name = "platformdirs" -version = "4.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/bb/477b7d60381d97a4ba45ae1bcedd6eeb1f689bea82034f80bbdc9634d639/py-serializable-0.15.0.tar.gz", hash = "sha256:8fc41457d8ee5f5c5a12f41fd87bf1a4f2ecf9da39fee92059b728e78f320771", size = 19719, upload-time = "2023-10-10T07:12:02.677Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3e/430be46b4381b4768aa6b5f1d322db8bb48c0655763639b6d14979764ad2/py_serializable-0.15.0-py3-none-any.whl", hash = "sha256:d3f1201b33420c481aa83f7860c7bf2c2f036ba3ea82b6e15a96696457c36cd2", size = 19763, upload-time = "2023-10-10T07:12:01.392Z" }, ] [[package]] -name = "pluggy" -version = "1.6.0" +name = "pycares" +version = "5.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/a0/9c823651872e6a0face3f0311de2a40c8bbcb9c8dcb15680bd019ac56ac7/pycares-5.0.1.tar.gz", hash = "sha256:5a3c249c830432631439815f9a818463416f2a8cbdb1e988e78757de9ae75081", size = 652222, upload-time = "2026-01-01T12:37:00.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d6/0c6b03ca9456682a582b52a9525664006b2e5041753a83a238209c705ea0/pycares-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:adc592534a10fe24fd1a801173c46769f75b97c440c9162f5d402ee1ba3eaf51", size = 136174, upload-time = "2026-01-01T12:34:57.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/fb5ce224458033494de5ce4302281d70276c4700a2d130b05f8f033e6640/pycares-5.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8848bbea6b5c2a0f7c9d0231ee455c3ce976c5c85904e014b2e9aa636a34140e", size = 130956, upload-time = "2026-01-01T12:34:58.543Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/00a752e86bf4e2eb3bf0c6607ba3500c4d72fd1d2b55c59981a56f6e818e/pycares-5.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5003cbbae0a943f49089cc149996c3d078cef482971d834535032d53558f4d48", size = 220639, upload-time = "2026-01-01T12:34:59.781Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8e/bb01efa0367230ff4876b19080aea7b41ae06ef0f33b5413037c0bd5b946/pycares-5.0.1-cp310-cp310-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc0cdeadb2892e7f0ab30b6a288a357441c21dcff2ce16e91fccbc9fae9d1e2a", size = 252214, upload-time = "2026-01-01T12:35:01.205Z" }, + { url = "https://files.pythonhosted.org/packages/92/ee/11cf3d9b133874b7724562fea4a28c735fbfeede01b10748d0adf64f38ec/pycares-5.0.1-cp310-cp310-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:faa093af3bea365947325ec23ed24efe81dcb0efc1be7e19a36ba37108945237", size = 239089, upload-time = "2026-01-01T12:35:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/84/71/138c92209df02e30bf00819ee1a25c495bceacdfeb72e3fe5575fc974129/pycares-5.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dedd6d41bd09dbed7eeea84a30b4b6fd1cacf9523a3047e088b5e692cff13d84", size = 222909, upload-time = "2026-01-01T12:35:03.941Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/2d2ade510564abad2b47a9aa451d81ae503bddf4e0831097346aaa5fffe7/pycares-5.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d3eb5e6ba290efd8b543a2cb77ad938c3494250e6e0302ee2aa55c06bbe153cd", size = 223515, upload-time = "2026-01-01T12:35:05.126Z" }, + { url = "https://files.pythonhosted.org/packages/37/9f/f1389f7fcec9f7e57c409a39d3dd8c5a8e6ad82b50ae95a2253e538a0eca/pycares-5.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:58634f83992c81f438987b572d371825dae187d3a09d6e213edbe71fbb4ba18c", size = 251670, upload-time = "2026-01-01T12:35:06.425Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1e/e98efb49c11070dc41c32b1b5a2e1438431656c361d789efda35ccd9c9a6/pycares-5.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fe9ce4361809903261c4b055284ba91d94adedfd2202e0257920b9085d505e37", size = 237746, upload-time = "2026-01-01T12:35:07.372Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/47e75c421d8fb6c7de4bc020fda10401b0d7aa88e77dbb3c3606391d844e/pycares-5.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:965ec648814829788233155ef3f6d4d7e7d6183460d10f9c71859c504f8f488b", size = 222650, upload-time = "2026-01-01T12:35:08.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/abb03c2620c4cc0e2eca0b42c751522d22087fe00d5a027c68c1ca0b5603/pycares-5.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:171182baa32951fffd1568ba9b934a76f36ed86c6248855ec6f82bbb3954d604", size = 117440, upload-time = "2026-01-01T12:35:09.286Z" }, + { url = "https://files.pythonhosted.org/packages/05/d3/7e005c6b23c1f6f48402b3b41d1ba2b129c593bb13993d7e087e577b8389/pycares-5.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:48ac858124728b8bac0591aa8361c683064fefe35794c29b3a954818c59f1e9b", size = 108921, upload-time = "2026-01-01T12:35:10.417Z" }, + { url = "https://files.pythonhosted.org/packages/87/78/43b09f4b8e5fb8a6024661b458b48987abdb39304c78117b106b10a029f1/pycares-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c29ca77ff9712e20787201ca8e76ad89384771c0e058a0a4f3dc05afbc4b32de", size = 136177, upload-time = "2026-01-01T12:35:11.567Z" }, + { url = "https://files.pythonhosted.org/packages/19/05/194c0e039ff52b166b50e79ff166c61f931fbca2bf94fc0dbaaf39041518/pycares-5.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f11424bf5cf6226d0b136ed47daa58434e377c61b62d0100d1de7793f8e34a72", size = 130960, upload-time = "2026-01-01T12:35:12.828Z" }, + { url = "https://files.pythonhosted.org/packages/0d/84/5fce65cc058c5ab619c0dd1370d539667235a5565da72ca77f3f741cdc70/pycares-5.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d765afb52d579879f5c4f005763827d3b1eb86b23139e9614e6089c9f98db017", size = 220584, upload-time = "2026-01-01T12:35:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/f6/74/d82304297308f6c24a17961bf589b53eefa5f7f2724158c842c67fa0b302/pycares-5.0.1-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ea0d57ba5add4bfbcc40cbdfa92bbb8a5ef0c4c21881e26c7229d9bdc92a4533", size = 252166, upload-time = "2026-01-01T12:35:15.293Z" }, + { url = "https://files.pythonhosted.org/packages/39/a2/0ead3ba4228a490b52eb44d43514dae172c90421bb30a3659516e5b251a2/pycares-5.0.1-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9ec2aa3553d33e6220aeb1a05f4853fb83fce4cec3e0dea2dc970338ea47dc", size = 239085, upload-time = "2026-01-01T12:35:16.594Z" }, + { url = "https://files.pythonhosted.org/packages/26/ad/e59f173933f0e696a6afbbd63935114d1400524a72da4f2cbafc6002a398/pycares-5.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c63fb2498b05e9f5670a1bf3b900c5d09343b3b6d5001a9714d593f9eb54de1", size = 222936, upload-time = "2026-01-01T12:35:17.521Z" }, + { url = "https://files.pythonhosted.org/packages/98/fa/d85bfe663a9c292efd8e699779027612c0c65ff50dc4cc9eb7a143613460/pycares-5.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71316f7a87c15a8d32127ff01374dc2c969c37410693cc0cf6532590b7f18e7a", size = 223506, upload-time = "2026-01-01T12:35:18.535Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6b/4c225a5b10a4c9f88891a20bfe363eca1b1ce7d5244b396e5683c6070998/pycares-5.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a2117dffbb78615bfdb41ad77b17038689e4e01c66f153649e80d268c6228b4f", size = 251633, upload-time = "2026-01-01T12:35:19.819Z" }, + { url = "https://files.pythonhosted.org/packages/26/ce/ba2349413b5197b72ec19c46e07f6be3a324f80a7b1579c7cbb1b82d6dc2/pycares-5.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7d7c4f5d8b88b586ef2288142b806250020e6490b9f2bd8fd5f634a78fd20fcf", size = 237703, upload-time = "2026-01-01T12:35:20.827Z" }, + { url = "https://files.pythonhosted.org/packages/84/2f/1fd794e6fca10d9e20569113d10a4f92cc2b4242d3eb45524419a37cca6b/pycares-5.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433b9a4b5a7e10ef8aef0b957e6cd0bfc1bb5bc730d2729f04e93c91c25979c0", size = 222622, upload-time = "2026-01-01T12:35:22.518Z" }, + { url = "https://files.pythonhosted.org/packages/c9/07/7db7977649b210092a7e02d550fcebdfa69bc995c684a3b960c88a5dc4ce/pycares-5.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf2699883b88713670d3f9c0a1e44ac24c70aeace9f8c6aa7f0b9f222d5b08a5", size = 117438, upload-time = "2026-01-01T12:35:23.402Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ca/f322ddaa8b3414667de8faeea944ce9d3ddfaf1455839f499a21fcea4cec/pycares-5.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:9528dc11749e5e098c996475b60f879e1db5a6cb3dd0cdc747530620bb1a8941", size = 108920, upload-time = "2026-01-01T12:35:24.599Z" }, + { url = "https://files.pythonhosted.org/packages/75/67/e84ba11d3fec3bf1322c3b302c4df13c85e0a1bc48f16d65cd0f59ad9853/pycares-5.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ee551be4f3f3ac814ac8547586c464c9035e914f5122a534d25de147fa745e1", size = 136241, upload-time = "2026-01-01T12:35:25.439Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ae/50fbb3b4e52b9f1d16a36ffabd051ef8b2106b3f0a0d1c1113904d187a9d/pycares-5.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:252d4e5a52a68f825eaa90e16b595f9baee22c760f51e286ab612c6829b96de3", size = 131069, upload-time = "2026-01-01T12:35:26.293Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ea/f431599f1ac42149ea4768e516db7cdae3a503a6646319ae63ab66da1486/pycares-5.0.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c1aa549b8c2f2e224215c793d660270778dcba9abc3b85abbc7c41eabe4f1e5", size = 221120, upload-time = "2026-01-01T12:35:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4f/0a7a6c8b3a64ee5149e935c167cd8ba5d1fdd766ec03e273dbc7502f7bea/pycares-5.0.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:db7c9c9f16e8311998667a7488e817f8cbeedec2447bac827c71804663f1437e", size = 252228, upload-time = "2026-01-01T12:35:28.443Z" }, + { url = "https://files.pythonhosted.org/packages/49/3d/7f9fd20e97ee30c4b959f87ab26e47ddcef666e5e7717e45f2245fe9d70a/pycares-5.0.1-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9c4c8bb69bab863f677fa166653bb872bfa5d5a742f1f30bebc2d53b6e71db", size = 239473, upload-time = "2026-01-01T12:35:29.794Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d0/c67967a10abd89529cb9aded9d73f43e5de00cf21243638ef529f6757262/pycares-5.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09ef90da8da3026fcba4ed223bd71e8057608d5b3fec4f5990b52ae1e8c855cc", size = 223831, upload-time = "2026-01-01T12:35:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9a/94aacaf22a20b7d342c8f18bf006be57967beef6319adc668d4d86b627be/pycares-5.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ce193ebd54f4c74538b751ebb0923a9208c234ff180589d4d3cec134c001840e", size = 223963, upload-time = "2026-01-01T12:35:31.691Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e1/3666aab6fc5e7d0c669b981fe0407e6a4b67e4e6a37ac429d440274663d5/pycares-5.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:36b9ff18ef231277f99a846feade50b417187a96f742689a9d08b9594e386de4", size = 251813, upload-time = "2026-01-01T12:35:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/ddab5fbc16ad0084a827167ae8628f54c7a55ce6b743585e6f47a5dd527e/pycares-5.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5e40ea4a0ef0c01a02ef7f7390a58c62d237d5ad48d36bc3245e9c2ac181cc22", size = 238181, upload-time = "2026-01-01T12:35:34.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/05467933e0e5c4e712302a2d7499797bc3029bf4d0d8ffbfe737254482b7/pycares-5.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f323b0ddfd2c7896af6fba4f8851d34d3d13387566aa573d93330fb01cb1038", size = 223552, upload-time = "2026-01-01T12:35:35.076Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e2/14f3837e943d46ee12441fe6aaa418fdb2f698d42e179f368eaa9829744b/pycares-5.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bdc6bcafb72a97b3cdd529fc87210e59e67feb647a7e138110656023599b84da", size = 117478, upload-time = "2026-01-01T12:35:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c3/3284061f18188d5085338e1f1fd4f03d9c135657acf16f8020b9dd3be5fc/pycares-5.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:f8ef4c70c1edaf022875a8f9ff6c0c064f82831225acc91aa1b4f4d389e2e03a", size = 108889, upload-time = "2026-01-01T12:35:37.135Z" }, + { url = "https://files.pythonhosted.org/packages/92/0a/6bd9bdc2d0ee23ff3aabab7747212e2c5323a081b9b745624d62df88f7e9/pycares-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d1b2c6b152c65f14d0e12d741fabb78a487f0f0d22773eede8d8cfc97af612b", size = 136242, upload-time = "2026-01-01T12:35:38.372Z" }, + { url = "https://files.pythonhosted.org/packages/18/2a/2e9f888fc076cfe7a3493a3c4113e787cc4b4533f531dfb562ac9b04898f/pycares-5.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c8ffcc9a48cfc296fe1aefc07d2c8e29a7f97e4bb366ce17effea6a38825f70", size = 131070, upload-time = "2026-01-01T12:35:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/5b/83b5aaf7b6ed102f63cd768a747b6cb5d4624f2eaecd84868d103b9dbf39/pycares-5.0.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8efc38c2703e3530b823a4165a7b28d7ce0fdcf41960fb7a4ca834a0f8cfe79", size = 221137, upload-time = "2026-01-01T12:35:40.155Z" }, + { url = "https://files.pythonhosted.org/packages/33/d3/d77ab0b33fb805d02896c385176c462e3386d94457a5e508245c39f41829/pycares-5.0.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e380bf6eff42c260f829a0a14547e13375e949053a966c23ca204a13647ef265", size = 252252, upload-time = "2026-01-01T12:35:41.287Z" }, + { url = "https://files.pythonhosted.org/packages/14/32/8afbc798bce26dfcc5bc1f6bf1560d31cdd0af837ff52cbede657bf9262e/pycares-5.0.1-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:35dd5858ee1246bd092a212b5e85a8ef70853f7cfaf16b99569bf4af3ae4695d", size = 239447, upload-time = "2026-01-01T12:35:42.614Z" }, + { url = "https://files.pythonhosted.org/packages/61/1b/a056393fda383b2eda5dab20bd0dd034fd631bf5ae754aabb20da815bdfe/pycares-5.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c257c6e7bf310cdb5823aa9d9a28f1e370fed8c653a968d38a954a8f8e0375ce", size = 223822, upload-time = "2026-01-01T12:35:43.594Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c7/9817f0fb954ab9926f88403f2b91a3e4984a277e2b7a4563e0118e4e1ffa/pycares-5.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07711acb0ef75758f081fb7436acaccc91e8afd5ae34fd35d4edc44297e81f27", size = 223986, upload-time = "2026-01-01T12:35:44.893Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a9/c0ea15c871c77e8c20bcaab18f56ae83988ea4c302155d106cc6a1bd83a9/pycares-5.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:30e5db1ae85cffb031dd8bc1b37903cd74c6d37eb737643bbca3ff2cd4bc6ae2", size = 251838, upload-time = "2026-01-01T12:35:46.271Z" }, + { url = "https://files.pythonhosted.org/packages/be/a4/fe4068abfadf3e06cc22333e87e4730de3c170075572041d5545926062a3/pycares-5.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:efbe7f89425a14edbc94787042309be77cb3674415eb6079b356e1f9552ba747", size = 238238, upload-time = "2026-01-01T12:35:47.196Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/4f140518768d974af4221cfd574a30d99d40b3d5c54c479da2c1553be59e/pycares-5.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5de9e7ce52d638d78723c24704eb032e60b96fbb6fe90c6b3110882987251377", size = 223574, upload-time = "2026-01-01T12:35:48.191Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0a/6e4afa4a2baffd1eba6c18a90cda17681d4838d3cab5a485e471386e04dc/pycares-5.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:0e99af0a1ce015ab6cc6bd85ce158d95ed89fb3b654515f1d0989d1afcf11026", size = 117472, upload-time = "2026-01-01T12:35:50.674Z" }, + { url = "https://files.pythonhosted.org/packages/57/d0/a99f97e9aa8c8404fc899540cf30be63cda0df5150e3c0837423917c7e4c/pycares-5.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:2a511c9f3b11b7ce9f159c956ea1b8f2de7f419d7ca9fa24528d582cb015dbf9", size = 108889, upload-time = "2026-01-01T12:35:51.902Z" }, + { url = "https://files.pythonhosted.org/packages/38/b2/4af99ff17acb81377c971831520540d1859bf401dc85712eb4abc2e6751f/pycares-5.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e330e3561be259ad7a1b7b0ce282c872938625f76587fae7ac8d6bc5af1d0c3d", size = 136635, upload-time = "2026-01-01T12:35:53.365Z" }, + { url = "https://files.pythonhosted.org/packages/42/da/e2e1683811c427492ee0e86e8fae8d55eb5cca032220438599991fdad866/pycares-5.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82bd37fec2a3fa62add30d4a3854720f7b051386e2f18e6e8f4ee94b89b5a7b0", size = 131093, upload-time = "2026-01-01T12:35:54.28Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2a/9cf2120cafc19e5c589d5252a9ddd3108cc87e9db09938d16317807de03b/pycares-5.0.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:258c38aaa82ad1d565b4591cdb93d2c191be8e0a2c70926999c8e0b717a01f2a", size = 221096, upload-time = "2026-01-01T12:35:57.096Z" }, + { url = "https://files.pythonhosted.org/packages/2c/cc/c5fbf6377e2d6b1f1618f147ad898e5d8ae1585fc726d6301f07aeda6cac/pycares-5.0.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ccc1b2df8a09ca20eefbe20b9f7a484d376525c0fb173cfadd692320013c6bc5", size = 252330, upload-time = "2026-01-01T12:35:58.182Z" }, + { url = "https://files.pythonhosted.org/packages/3b/df/17a7c518c45bb994f76d9064d2519674e2a3950f895abbe6af123ead04ac/pycares-5.0.1-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c4dfc80cc8b43dc79e02a15486c58eead5cae0a40906d6be64e2522285b5b39", size = 239799, upload-time = "2026-01-01T12:36:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6c/d79c94809742b56b9180a9a9ec2937607db0b8eb34b8ca75d86d3114d6dd/pycares-5.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f498a6606247bfe896c2a4d837db711eb7b0ba23e409e16e4b23def4bada4b9d", size = 223501, upload-time = "2026-01-01T12:36:02.695Z" }, + { url = "https://files.pythonhosted.org/packages/69/08/83084b67cbce08f44fd803b88816fc80d2fe2fb3d483d5432925df44371b/pycares-5.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7d197835cdb4b202a3b12562b32799e27bb132262d4aa1ac3ee9d440e8ec22c", size = 223708, upload-time = "2026-01-01T12:36:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/63a6e9ef356c5149b8ec72a694e02207fd8ae643895aeb78a9f0c07f1502/pycares-5.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f78ab823732b050d658eb735d553726663c9bccdeeee0653247533a23eb2e255", size = 251816, upload-time = "2026-01-01T12:36:05.618Z" }, + { url = "https://files.pythonhosted.org/packages/43/1c/1c85c6355cf7bc3ae86a1024d60f9cabdc12af63306a5f59370ac8718a41/pycares-5.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f444ab7f318e9b2c209b45496fb07bff5e7ada606e15d5253a162964aa078527", size = 238259, upload-time = "2026-01-01T12:36:07.609Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7f/bd5ff5a460e50433f993560e4e5d229559a8bf271dbdf6be832faf1973b5/pycares-5.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de80997de7538619b7dd28ec4371e5172e3f9480e4fc648726d3d5ba661ca05", size = 223732, upload-time = "2026-01-01T12:36:09.893Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/e77738366e00dc0918bbeb0c8fc63579e5d9cec748a2b838e207e548b5d9/pycares-5.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:206ce9f3cb9d51f5065c81b23c22996230fbc2cf58ae22834c623631b2b473aa", size = 120847, upload-time = "2026-01-01T12:36:11.494Z" }, + { url = "https://files.pythonhosted.org/packages/81/17/758e9af7ee8589ac6deddf7ea56d75b982f155bc2052ef61c45d5f371389/pycares-5.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:45fb3b07231120e8cb5b75be7f15f16115003e9251991dc37a3e5c63733d63b5", size = 112595, upload-time = "2026-01-01T12:36:12.973Z" }, + { url = "https://files.pythonhosted.org/packages/56/12/4f1d418fed957fc96089c69d9ec82314b3b91c48c7f9463385842acad9c4/pycares-5.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:602f3eac4b880a2527d21f52b2319cb10fde9225d103d338c4d0b2b07f136849", size = 137061, upload-time = "2026-01-01T12:36:15.027Z" }, + { url = "https://files.pythonhosted.org/packages/29/8c/559cea98a8a5d0f38b50b4b812a07fdbcdb1a961bed9e2e9d5d343e53c6f/pycares-5.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1c3736deef003f0c57bc4e7f94d54270d0824350a8f5ceaba3a20b2ce8fb427", size = 131551, upload-time = "2026-01-01T12:36:16.74Z" }, + { url = "https://files.pythonhosted.org/packages/34/cd/aee5d8070888d7be509d4f32a348e2821309ec67980498e5a974cd9e4990/pycares-5.0.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e63328df86d37150ce697fb5d9313d1d468dd4dddee1d09342cb2ed241ce6ad9", size = 230409, upload-time = "2026-01-01T12:36:18.909Z" }, + { url = "https://files.pythonhosted.org/packages/5e/94/15d5cf7d8e7af4b4ce3e19ea117dfe565c08d60d82f043ad23843703a135/pycares-5.0.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57f6fd696213329d9a69b9664a68b1ff2a71ccbdc1fc928a42c9a92858c1ec5d", size = 261297, upload-time = "2026-01-01T12:36:20.771Z" }, + { url = "https://files.pythonhosted.org/packages/af/46/24f6ddc7a37ec6eaa1c38f617f39624211d8e7cdca49b644bfc5f467f275/pycares-5.0.1-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d0878edabfbecb48a29e8769284003d8dbc05936122fe361849cd5fa52722e0", size = 248071, upload-time = "2026-01-01T12:36:22.925Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/7eb7fe44f0db55b9083725ab7a084874c2dc02806d9613e07e719838c2ab/pycares-5.0.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50e21f27a91be122e066ddd78c2d0d2769e547561481d8342a9d652a345b89f7", size = 232073, upload-time = "2026-01-01T12:36:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cd/993b17e0c049a56b5af4df3fd053acc57b37e17e0dcd709b2d337c22d57d/pycares-5.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:97ceda969f5a5d5c6b15558b658c29e4301b3a2c4615523797b5f9d4ac74772e", size = 232815, upload-time = "2026-01-01T12:36:27.798Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ff/170177bcc5dff31e735f209f5de63362f513ac18846c83d50e4e68f57866/pycares-5.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4d1713e602ab09882c3e65499b2cc763bff0371117327cad704cf524268c2604", size = 261111, upload-time = "2026-01-01T12:36:29.94Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4a/4c6497b8ca9279b4038ee8c7e2c49504008d594d06a044e00678b30c10fe/pycares-5.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:954a379055d6c66b2e878b52235b382168d1a3230793ff44454019394aecac5e", size = 246311, upload-time = "2026-01-01T12:36:31.352Z" }, + { url = "https://files.pythonhosted.org/packages/06/19/1603f51f0d73bf34017a9e6967540c2bc138f9541aa7cc1ef38990b3ce9d/pycares-5.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:145d8a20f7fd1d58a2e49b7ef4309ec9bdcab479ac65c2e49480e20d3f890c23", size = 232027, upload-time = "2026-01-01T12:36:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/7a/de/c000a682757b84688722ac232a24a86b6f195f1f4732432ecf35d0a768a5/pycares-5.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ebc9daba03c7ff3f62616c84c6cb37517445d15df00e1754852d6006039eb4a4", size = 121267, upload-time = "2026-01-01T12:36:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c4/8bfffecd08b9b198113fcff5f0ab84bbe696f07dec46dd1ccae0e7b28c23/pycares-5.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:e0a86eff6bf9e91d5dd8876b1b82ee45704f46b1104c24291d3dea2c1fc8ebcb", size = 113043, upload-time = "2026-01-01T12:36:37.895Z" }, ] [[package]] -name = "pre-commit" -version = "4.6.0" +name = "pycep-parser" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, + { name = "lark" }, + { name = "regex" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/fb/3912b366eaae9414758dda5c4b6903f3931bafe25490bd7c6a4a27409ea1/pycep_parser-0.4.1.tar.gz", hash = "sha256:a3edd1c3d280c283d614c865a854a693daf56c35cd4095b373016c214baa76dd", size = 21494, upload-time = "2023-06-18T17:51:45.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/18/2a/568fb2c093dffc15335eb3e2d622acb13366f694a7465bd537e93e4219a9/pycep_parser-0.4.1-py3-none-any.whl", hash = "sha256:27c87ad875538eb17d696002b266d921ce8eb3f7fa3b7fc09c1b3c085009527f", size = 22448, upload-time = "2023-06-18T17:51:44.486Z" }, ] [[package]] -name = "properdocs" -version = "1.6.7" +name = "pycparser" +version = "3.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "ghp-import" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "watchdog" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/29/f27a4e1eddf72ed3db6e47818fbafe6debbf09fd7051f9c1a007239b46ef/properdocs-1.6.7.tar.gz", hash = "sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e", size = 276141, upload-time = "2026-03-20T20:07:48.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/4d/fc923f5c85318ee8cc903566dc4e0ebe41b2dfc1d2ecf5546db232397ed6/properdocs-1.6.7-py3-none-any.whl", hash = "sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd", size = 225406, upload-time = "2026-03-20T20:07:46.875Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -1417,17 +3190,34 @@ wheels = [ { 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]] +name = "pyjwt" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pymdown-extensions" -version = "10.21.2" +version = "10.21.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, + { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, ] [[package]] @@ -1439,6 +3229,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "pyston" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/74/c8ae8b7743e8d138e4ec0df114dc8b039b9fea964ab5d1e20de17012ec89/pyston-2.3.5-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:deb9dac7f8f67d1b2dc709300e1d8fda51bbc1375957509f58e1dc4459324ea7", size = 223822, upload-time = "2022-09-26T19:24:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/cf/16/24c4b46a3aa410fb8894f17db4159218918dcaa4051febd4fae57cd3bc7e/pyston-2.3.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30caaee3b58d92817efa2cd4f32c24289dd5f4ddf9b5b4ec5b62ed564230ca8a", size = 506426, upload-time = "2022-09-27T00:17:04.363Z" }, +] + +[[package]] +name = "pyston-autoload" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyston", marker = "python_full_version < '3.11'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/c7c423410da814acb78d15fd37a6c1de4b8dcfc8fa5c8c048cd1ba697f41/pyston_autoload-2.3.5-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:50c5d2e2855a542f9e427601ed1cc94aa1ff82937c001e5765f4db67af8a309a", size = 1550, upload-time = "2022-09-26T19:24:57.821Z" }, + { url = "https://files.pythonhosted.org/packages/ca/16/0e6691a2b9d7e4c3ce9003550e6ed84c67ae2032f6a67d80ad990708aced/pyston_autoload-2.3.5-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:14d09effb82c436b2b090cf3293e8279e14af065ed5383ab06f72d5481f89cfd", size = 1541, upload-time = "2022-09-27T00:17:20.273Z" }, +] + [[package]] name = "pytest" version = "9.0.3" @@ -1497,6 +3326,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1531,6 +3373,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.29" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/fe/70bd71a6738b09a0bdf6480ca6436b167469ca4578b2a0efbe390b4b0e70/python_multipart-0.0.29.tar.gz", hash = "sha256:643e93849196645e2dbdd81a0f8829a23123ad7f797a84a364c6fb3563f18904", size = 45678, upload-time = "2026-05-17T17:29:47.654Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/cb/769cfc37177252872a45a71f3fbdde9d51b471a3f3c14bfe95dde3407386/python_multipart-0.0.29-py3-none-any.whl", hash = "sha256:2ddcc971cef266225f54f552d8fa10bcfbb1f14446caec199060daac59ff2d69", size = 29640, upload-time = "2026-05-17T17:29:45.69Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1607,9 +3489,157 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +name = "rdflib" +version = "7.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "isodate", marker = "python_full_version < '3.11'" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/18bb77b7af9526add0c727a3b2048959847dc5fb030913e2918bf384fec3/rdflib-7.6.0.tar.gz", hash = "sha256:6c831288d5e4a5a7ece85d0ccde9877d512a3d0f02d7c06455d00d6d0ea379df", size = 4943826, upload-time = "2026-02-13T07:15:55.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/c2/6604a71269e0c1bd75656d5a001432d16f2cc5b8c057140ec797155c295e/rdflib-7.6.0-py3-none-any.whl", hash = "sha256:30c0a3ebf4c0e09215f066be7246794b6492e054e782d7ac2a34c9f70a15e0dd", size = 615416, upload-time = "2026-02-13T07:15:46.487Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.5.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ed/0ad2c8edf634918eb4484365d3819fa7bd7f58daf807fe7fb21812c316e5/regex-2026.5.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a9e1328e17c84c1a5d22ec9f785ecef4a967fab9a42b6a8dc3bcbebd0a0c9e44", size = 489438, upload-time = "2026-05-09T23:11:29.374Z" }, + { url = "https://files.pythonhosted.org/packages/89/a9/4ed972ad263963b860b7c3e86e0e1bcc791def47b43b8c8efe57e710f139/regex-2026.5.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfe1ce50cbfb569d74e1e4337da6468961f31dbea55fd85aa5de59c0947a805a", size = 291270, upload-time = "2026-05-09T23:11:33.254Z" }, + { url = "https://files.pythonhosted.org/packages/16/81/075930d9fa28c4ea1f53398dd015ee7c882f623539759113cda1257f4b82/regex-2026.5.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15ee42209947f4ca045412eae98416317238163618ace2a8e54f99586a466733", size = 289198, upload-time = "2026-05-09T23:11:35.769Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c8/5cdfbf0b5dc6599e1b6131eff43262e5275d4ec3469ce10216061659aadb/regex-2026.5.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb445ff3f725f59df8f6014edb547ee928ec7023a774f6a39a3f953038cbb2", size = 784765, upload-time = "2026-05-09T23:11:37.689Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ca/ae5fd6edc59b7f84b904b31d6ec39a860cbcecd10f64bd5a062ca83a4864/regex-2026.5.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:446ddd671e43ab535810c4b21cff7104945c701d4a14d1e6d1cd6f4e445a8bea", size = 852115, upload-time = "2026-05-09T23:11:39.973Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ce/a91cf555afb51f3b74a182e24ba073b91ea7bb64592fc4b315c111bb19fd/regex-2026.5.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b92817338591505f282cf3864c145244b1edcf5381d237038df955001091538", size = 899503, upload-time = "2026-05-09T23:11:42.48Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/725a0a2b245a4cf0c4bab29d0e97c74285d94136a65d1b55a6459a583502/regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b8a143aca6c39b446ea8092cde25cc8fe9304d4f5fecfbc1a9dbb0282703c2", size = 794093, upload-time = "2026-05-09T23:11:44.681Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2a/996efbd59ce6b5d4a09e3af6180ceb62af171f4a9a6fb557d2f0ae0d462b/regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0f03aa6898aaaac4592479821df16e68e8d0e29e903e65d8f2dfb2f19028a989", size = 786234, upload-time = "2026-05-09T23:11:46.882Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/8731e8b8806174c9cdd5903f80a14990331c1f42fc4209b540952e9e010d/regex-2026.5.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ed457d8e98ae812ed7732bef7bf78de78e834eae0372a74e23ca90ef21d910f9", size = 769895, upload-time = "2026-05-09T23:11:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/9a/0b/932473194bd563f342a412ae2ffbbd6da608306a2bc4e99249a41c2b0b92/regex-2026.5.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71b61c5bfe1c806332defc42ad6c780b3c55f661986d7f40283a3a88274b4c00", size = 774991, upload-time = "2026-05-09T23:11:51.261Z" }, + { url = "https://files.pythonhosted.org/packages/98/80/9523d196010031df25f7177ee0a467efbee436324038e5d99def17a57515/regex-2026.5.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3b1e39888c5e0c7d92cea4fc777396c4a90363b05de75d02eb459a4752200808", size = 848790, upload-time = "2026-05-09T23:11:53.232Z" }, + { url = "https://files.pythonhosted.org/packages/3c/07/56987b35e89edf47e4a38cf2845aeee476bfa688a6bdbd3e820cda461dc1/regex-2026.5.9-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6ba42b2e7e7f46cf68cc6a5ca36fa07959f9bbd9c6bdcc47b6ee76549a590248", size = 757679, upload-time = "2026-05-09T23:11:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/04/2a/ff713fff0c566507c06a4ce2dc0ae8e7eeebc88811a95fc81cf1e7d534dd/regex-2026.5.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c010eb8caca74bdb40c07498d7ece26b4428fd3f04aa8a72c9ac6f79e8faaac6", size = 837116, upload-time = "2026-05-09T23:11:57.934Z" }, + { url = "https://files.pythonhosted.org/packages/77/90/df6d982b03e3614785c6937ba51b57f6733d97d2ee1c9bc7531dbfab3a54/regex-2026.5.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a6a563446a41adc451393dc6b8e6ad87979efaee3c8738690a8d1b08ebead1b4", size = 782081, upload-time = "2026-05-09T23:11:59.607Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/4e88a5f7c3e98489aac4dd23142723d907b2a595b4a6abcbacabefeded09/regex-2026.5.9-cp310-cp310-win32.whl", hash = "sha256:954cc214c04663ee6d266fc61739cad83054683048de65c5bd1d640ad28098ac", size = 266247, upload-time = "2026-05-09T23:12:01.116Z" }, + { url = "https://files.pythonhosted.org/packages/6a/40/4b224cb0582b2dca1786726e6cdabe26abbf757d7f6718332f186da155d2/regex-2026.5.9-cp310-cp310-win_amd64.whl", hash = "sha256:b310768746dd314ea6e2ff4cc89ef215426813396ff4e94ee8e6f7096c8b6e03", size = 278416, upload-time = "2026-05-09T23:12:03.2Z" }, + { url = "https://files.pythonhosted.org/packages/12/4d/014fbe803204cab0947ee428f09f658a29632053dde1d3c6176bb4f0fd4c/regex-2026.5.9-cp310-cp310-win_arm64.whl", hash = "sha256:19c16ceb4a267a8789e25733e583983eeab9f0f8664e66b0bd1c5d21f14c2d4b", size = 270413, upload-time = "2026-05-09T23:12:04.649Z" }, + { url = "https://files.pythonhosted.org/packages/c2/dc/c1f2df4027e82fc54b5a473e4b250f5139faca49a0fbe29a48668d228f34/regex-2026.5.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48", size = 489445, upload-time = "2026-05-09T23:12:06.111Z" }, + { url = "https://files.pythonhosted.org/packages/03/d2/59f01110660081cce9c0bc30ebd0b5ee250dacf658e3248ed92f01e0e8ee/regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", size = 291271, upload-time = "2026-05-09T23:12:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/58/b6/14b2c84ff90ddb370c81d27503f4a0fcf071496416f4855f6cc8c5d81c35/regex-2026.5.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555", size = 289212, upload-time = "2026-05-09T23:12:09.266Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/4db86529117320de0c84afd90e70bb47434625875e34fcef9d8c127c5b16/regex-2026.5.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919", size = 792310, upload-time = "2026-05-09T23:12:11.416Z" }, + { url = "https://files.pythonhosted.org/packages/07/78/fe4800cd322f862ecffd2d553409b20d80650e5ed71b9d178f853d020b82/regex-2026.5.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451", size = 861721, upload-time = "2026-05-09T23:12:13.681Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d0/b3618a895dd8feb897c61bb2954edd265e1767d82a01d53065d5871127a3/regex-2026.5.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c", size = 906460, upload-time = "2026-05-09T23:12:15.443Z" }, + { url = "https://files.pythonhosted.org/packages/33/6f/1481597e859ef19508b345eec4afd1416ed6e6b459c75a64026ef193aecf/regex-2026.5.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc", size = 799843, upload-time = "2026-05-09T23:12:16.892Z" }, + { url = "https://files.pythonhosted.org/packages/73/59/955734c803f59108deccba3597ae440c76b62a652733c0006e6243758420/regex-2026.5.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d", size = 773610, upload-time = "2026-05-09T23:12:19.127Z" }, + { url = "https://files.pythonhosted.org/packages/68/8f/70c04a236d651c81881dac42ef8538bddda6121434509d0a22d9e601503b/regex-2026.5.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9", size = 781645, upload-time = "2026-05-09T23:12:20.806Z" }, + { url = "https://files.pythonhosted.org/packages/1d/96/05c7434d88185e5d27fe54aeb74df86bd77cd79f52f0b4eae54faa8fea70/regex-2026.5.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2", size = 854473, upload-time = "2026-05-09T23:12:22.465Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/6e3d8202d981f3117004bf341ee74893ba4ba8a9fbaf4b94615846550a08/regex-2026.5.9-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf", size = 763311, upload-time = "2026-05-09T23:12:24.351Z" }, + { url = "https://files.pythonhosted.org/packages/93/c7/e7737f1526b3fb32bd4c337fd6c71c3ebb5c8296fc34d11197e0955d2e35/regex-2026.5.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611", size = 844593, upload-time = "2026-05-09T23:12:26.341Z" }, + { url = "https://files.pythonhosted.org/packages/a5/27/0daffb1a535bb39f422c3d200f4ab023c71110ad66a32b366bee708baba0/regex-2026.5.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c", size = 789167, upload-time = "2026-05-09T23:12:27.975Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fc/294fe4fac4f2ed67207b17471815870c1c45b3a489e08e0ac96daea16ef6/regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", size = 266249, upload-time = "2026-05-09T23:12:30.141Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b0/8dce459f6245bcf8f6e9f23ac9569f1a0f15c131cc0745e82b43226204cf/regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", size = 278423, upload-time = "2026-05-09T23:12:31.676Z" }, + { url = "https://files.pythonhosted.org/packages/db/8d/f9aeff6ad63a3ef720386f2907e6d34a35a510a6e498ebad28b0fb3f6ab6/regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", size = 270420, upload-time = "2026-05-09T23:12:33.194Z" }, + { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, + { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, + { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, + { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, + { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, + { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, + { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, + { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, + { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, + { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, + { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, + { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, + { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, + { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, + { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, + { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, + { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, + { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, + { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, + { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, + { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, + { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, +] + [[package]] name = "requests" -version = "2.34.0" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1617,9 +3647,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/b8/7a707d60fea4c49094e40262cc0e2ca6c768cca21587e34d3f705afec47e/requests-2.34.0.tar.gz", hash = "sha256:7d62fe92f50eb82c529b0916bb445afa1531a566fc8f35ffdc64446e771b856a", size = 142436, upload-time = "2026-05-11T19:29:51.717Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl", hash = "sha256:917520a21b767485ce7c588f4ebb917c436b24a31231b44228715eaeb5a52c60", size = 73021, upload-time = "2026-05-11T19:29:49.923Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -1647,6 +3677,128 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + [[package]] name = "ruamel-yaml" version = "0.19.1" @@ -1657,41 +3809,379 @@ wheels = [ ] [[package]] -name = "ruff" -version = "0.15.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +name = "ruamel-yaml-clib" +version = "0.2.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/5a/4ab767cd42dcd65b83c323e1620d7c01ee60a52f4032fb7b61501f45f5c2/ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03", size = 147454, upload-time = "2025-11-16T16:13:02.54Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/184173ac1e74fd35d308108bcbf83904d6ef8439c70763189225a166b238/ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77", size = 132467, upload-time = "2025-11-16T16:13:03.539Z" }, + { url = "https://files.pythonhosted.org/packages/49/1b/2d2077a25fe682ae335007ca831aff42e3cbc93c14066675cf87a6c7fc3e/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614", size = 693454, upload-time = "2025-11-16T20:22:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/90/16/e708059c4c429ad2e33be65507fc1730641e5f239fb2964efc1ba6edea94/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3", size = 700345, upload-time = "2025-11-16T16:13:04.771Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/0e8ef51df1f0950300541222e3332f20707a9c210b98f981422937d1278c/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da3d6adadcf55a93c214d23941aef4abfd45652110aed6580e814152f385b862", size = 731306, upload-time = "2025-11-16T16:13:06.312Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f4/2cdb54b142987ddfbd01fc45ac6bd882695fbcedb9d8bbf796adc3fc3746/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9fde97ecb7bb9c41261c2ce0da10323e9227555c674989f8d9eb7572fc2098d", size = 692415, upload-time = "2025-11-16T16:13:07.465Z" }, + { url = "https://files.pythonhosted.org/packages/a0/07/40b5fc701cce8240a3e2d26488985d3bbdc446e9fe397c135528d412fea6/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6", size = 705007, upload-time = "2025-11-16T20:22:42.856Z" }, + { url = "https://files.pythonhosted.org/packages/82/19/309258a1df6192fb4a77ffa8eae3e8150e8d0ffa56c1b6fa92e450ba2740/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed", size = 723974, upload-time = "2025-11-16T16:13:08.72Z" }, + { url = "https://files.pythonhosted.org/packages/67/3a/d6ee8263b521bfceb5cd2faeb904a15936480f2bb01c7ff74a14ec058ca4/ruamel_yaml_clib-0.2.15-cp310-cp310-win32.whl", hash = "sha256:fe239bdfdae2302e93bd6e8264bd9b71290218fff7084a9db250b55caaccf43f", size = 102836, upload-time = "2025-11-16T16:13:10.27Z" }, + { url = "https://files.pythonhosted.org/packages/ed/03/92aeb5c69018387abc49a8bb4f83b54a0471d9ef48e403b24bac68f01381/ruamel_yaml_clib-0.2.15-cp310-cp310-win_amd64.whl", hash = "sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd", size = 121917, upload-time = "2025-11-16T16:13:12.145Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, + { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, + { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, + { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, + { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, + { url = "https://files.pythonhosted.org/packages/17/5e/2f970ce4c573dc30c2f95825f2691c96d55560268ddc67603dc6ea2dd08e/ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb", size = 147450, upload-time = "2025-11-16T16:13:33.542Z" }, + { url = "https://files.pythonhosted.org/packages/d6/03/a1baa5b94f71383913f21b96172fb3a2eb5576a4637729adbf7cd9f797f8/ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471", size = 133139, upload-time = "2025-11-16T16:13:34.587Z" }, + { url = "https://files.pythonhosted.org/packages/dc/19/40d676802390f85784235a05788fd28940923382e3f8b943d25febbb98b7/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25", size = 731474, upload-time = "2025-11-16T20:22:49.934Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bb/6ef5abfa43b48dd55c30d53e997f8f978722f02add61efba31380d73e42e/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a", size = 748047, upload-time = "2025-11-16T16:13:35.633Z" }, + { url = "https://files.pythonhosted.org/packages/ff/5d/e4f84c9c448613e12bd62e90b23aa127ea4c46b697f3d760acc32cb94f25/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf", size = 782129, upload-time = "2025-11-16T16:13:36.781Z" }, + { url = "https://files.pythonhosted.org/packages/de/4b/e98086e88f76c00c88a6bcf15eae27a1454f661a9eb72b111e6bbb69024d/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d", size = 736848, upload-time = "2025-11-16T16:13:37.952Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5c/5964fcd1fd9acc53b7a3a5d9a05ea4f95ead9495d980003a557deb9769c7/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf", size = 741630, upload-time = "2025-11-16T20:22:51.718Z" }, + { url = "https://files.pythonhosted.org/packages/07/1e/99660f5a30fceb58494598e7d15df883a07292346ef5696f0c0ae5dee8c6/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd4c928ddf6bce586285daa6d90680b9c291cfd045fc40aad34e445d57b1bf51", size = 766619, upload-time = "2025-11-16T16:13:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/fa0344a9327b58b54970e56a27b32416ffbcfe4dcc0700605516708579b2/ruamel_yaml_clib-0.2.15-cp313-cp313-win32.whl", hash = "sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec", size = 100171, upload-time = "2025-11-16T16:13:40.456Z" }, + { url = "https://files.pythonhosted.org/packages/06/c4/c124fbcef0684fcf3c9b72374c2a8c35c94464d8694c50f37eef27f5a145/ruamel_yaml_clib-0.2.15-cp313-cp313-win_amd64.whl", hash = "sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6", size = 118845, upload-time = "2025-11-16T16:13:41.481Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bd/ab8459c8bb759c14a146990bf07f632c1cbec0910d4853feeee4be2ab8bb/ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef", size = 147248, upload-time = "2025-11-16T16:13:42.872Z" }, + { url = "https://files.pythonhosted.org/packages/69/f2/c4cec0a30f1955510fde498aac451d2e52b24afdbcb00204d3a951b772c3/ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf", size = 133764, upload-time = "2025-11-16T16:13:43.932Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/2480d062281385a2ea4f7cc9476712446e0c548cd74090bff92b4b49e898/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000", size = 730537, upload-time = "2025-11-16T20:22:52.918Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/e365ee305367559f57ba6179d836ecc3d31c7d3fdff2a40ebf6c32823a1f/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4", size = 746944, upload-time = "2025-11-16T16:13:45.338Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/8b56b08db91e569d0a4fbfa3e492ed2026081bdd7e892f63ba1c88a2f548/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c", size = 778249, upload-time = "2025-11-16T16:13:46.871Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1d/70dbda370bd0e1a92942754c873bd28f513da6198127d1736fa98bb2a16f/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043", size = 737140, upload-time = "2025-11-16T16:13:48.349Z" }, + { url = "https://files.pythonhosted.org/packages/5b/87/822d95874216922e1120afb9d3fafa795a18fdd0c444f5c4c382f6dac761/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524", size = 741070, upload-time = "2025-11-16T20:22:54.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/17/4e01a602693b572149f92c983c1f25bd608df02c3f5cf50fd1f94e124a59/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e", size = 765882, upload-time = "2025-11-16T16:13:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/9f/17/7999399081d39ebb79e807314de6b611e1d1374458924eb2a489c01fc5ad/ruamel_yaml_clib-0.2.15-cp314-cp314-win32.whl", hash = "sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa", size = 102567, upload-time = "2025-11-16T16:13:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/67/be582a7370fdc9e6846c5be4888a530dcadd055eef5b932e0e85c33c7d73/ruamel_yaml_clib-0.2.15-cp314-cp314-win_amd64.whl", hash = "sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467", size = 122847, upload-time = "2025-11-16T16:13:51.807Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, + { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, + { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, +] + +[[package]] +name = "rustarium" +source = { editable = "." } +dependencies = [ + { name = "loguru" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "typer" }, +] + +[package.optional-dependencies] +opentelemetry = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] + +[package.dev-dependencies] +dev = [ + { name = "checkov" }, + { name = "detect-secrets" }, + { name = "hatch" }, + { name = "mdformat" }, + { name = "mdformat-footnote" }, + { name = "mdformat-frontmatter" }, + { name = "mdformat-gfm" }, + { name = "mdformat-gfm-alerts" }, + { name = "mkdocs-gen-files" }, + { name = "phmdoctest" }, + { name = "pip-audit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-xdist" }, + { name = "respx" }, + { name = "ruff" }, + { name = "semgrep" }, + { name = "taplo" }, + { name = "ty" }, + { name = "urlchecker" }, + { name = "yamlfix" }, + { name = "yamllint" }, + { name = "zensical" }, +] +docs = [ + { name = "mkdocs-gen-files" }, + { name = "zensical" }, +] +environment = [ + { name = "hatch" }, +] +lint = [ + { name = "mdformat" }, + { name = "mdformat-footnote" }, + { name = "mdformat-frontmatter" }, + { name = "mdformat-gfm" }, + { name = "mdformat-gfm-alerts" }, + { name = "phmdoctest" }, + { name = "ruff" }, + { name = "taplo" }, + { name = "ty" }, + { name = "urlchecker" }, + { name = "yamlfix" }, + { name = "yamllint" }, +] +security = [ + { name = "checkov" }, + { name = "detect-secrets" }, + { name = "pip-audit" }, + { name = "semgrep" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-xdist" }, + { name = "respx" }, +] + +[package.metadata] +requires-dist = [ + { name = "loguru", specifier = ">=0.7.0" }, + { name = "opentelemetry-api", marker = "extra == 'opentelemetry'", specifier = ">=1.0.0" }, + { name = "opentelemetry-sdk", marker = "extra == 'opentelemetry'", specifier = ">=1.0.0" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pydantic-settings", specifier = ">=2.0.0" }, + { name = "typer", specifier = ">=0.12.0" }, +] +provides-extras = ["opentelemetry"] + +[package.metadata.requires-dev] +dev = [ + { name = "checkov", specifier = ">=3.0,<4" }, + { name = "detect-secrets", specifier = ">=1.5,<2" }, + { name = "hatch", specifier = ">=1.12.0" }, + { name = "mdformat", specifier = ">=1.0,<2" }, + { name = "mdformat-footnote", specifier = ">=0.1.1,<1" }, + { name = "mdformat-frontmatter", specifier = ">=2.0,<3" }, + { name = "mdformat-gfm", specifier = ">=1.0,<2" }, + { name = "mdformat-gfm-alerts", specifier = ">=1.0.1,<3" }, + { name = "mkdocs-gen-files", specifier = ">=0.5.0" }, + { name = "phmdoctest", specifier = ">=1.4,<2" }, + { name = "pip-audit", specifier = ">=2.7,<3" }, + { name = "pytest", specifier = ">=9.0,<10" }, + { name = "pytest-asyncio", specifier = ">=1.3,<2" }, + { name = "pytest-cov", specifier = ">=7.1,<8" }, + { name = "pytest-mock", specifier = ">=3.15,<4" }, + { name = "pytest-xdist", specifier = ">=3.5,<4" }, + { name = "respx", specifier = ">=0.23,<1" }, + { name = "ruff", specifier = ">=0.15,<1" }, + { name = "semgrep", specifier = ">=1.73,<2" }, + { name = "taplo" }, + { name = "ty" }, + { name = "urlchecker", specifier = ">=0.0.35" }, + { name = "yamlfix", specifier = ">=1.19,<2" }, + { name = "yamllint", specifier = ">=1.35,<2" }, + { name = "zensical", specifier = ">=0.0.40" }, +] +docs = [ + { name = "mkdocs-gen-files", specifier = ">=0.5.0" }, + { name = "zensical", specifier = ">=0.0.40" }, +] +environment = [{ name = "hatch", specifier = ">=1.12.0" }] +lint = [ + { name = "mdformat", specifier = ">=1.0,<2" }, + { name = "mdformat-footnote", specifier = ">=0.1.1,<1" }, + { name = "mdformat-frontmatter", specifier = ">=2.0,<3" }, + { name = "mdformat-gfm", specifier = ">=1.0,<2" }, + { name = "mdformat-gfm-alerts", specifier = ">=1.0.1,<3" }, + { name = "phmdoctest", specifier = ">=1.4,<2" }, + { name = "ruff", specifier = ">=0.15,<1" }, + { name = "taplo" }, + { name = "ty" }, + { name = "urlchecker", specifier = ">=0.0.35" }, + { name = "yamlfix", specifier = ">=1.19,<2" }, + { name = "yamllint", specifier = ">=1.35,<2" }, +] +security = [ + { name = "checkov", specifier = ">=3.0,<4" }, + { name = "detect-secrets", specifier = ">=1.5,<2" }, + { name = "pip-audit", specifier = ">=2.7,<3" }, + { name = "semgrep", specifier = ">=1.73,<2" }, +] +test = [ + { name = "pytest", specifier = ">=9.0,<10" }, + { name = "pytest-asyncio", specifier = ">=1.3,<2" }, + { name = "pytest-cov", specifier = ">=7.1,<8" }, + { name = "pytest-mock", specifier = ">=3.15,<4" }, + { name = "pytest-xdist", specifier = ">=3.5,<4" }, + { name = "respx", specifier = ">=0.23,<1" }, +] + +[[package]] +name = "rustworkx" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/b0/66d96f02120f79eeed86b5c5be04029b6821155f31ed4907a4e9f1460671/rustworkx-0.17.1.tar.gz", hash = "sha256:59ea01b4e603daffa4e8827316c1641eef18ae9032f0b1b14aa0181687e3108e", size = 399407, upload-time = "2025-09-15T16:29:46.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/24/8972ed631fa05fdec05a7bb7f1fc0f8e78ee761ab37e8a93d1ed396ba060/rustworkx-0.17.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c08fb8db041db052da404839b064ebfb47dcce04ba9a3e2eb79d0c65ab011da4", size = 2257491, upload-time = "2025-08-13T01:43:31.466Z" }, + { url = "https://files.pythonhosted.org/packages/23/ae/7b6bbae5e0487ee42072dc6a46edf5db9731a0701ed648db22121fb7490c/rustworkx-0.17.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:4ef8e327dadf6500edd76fedb83f6d888b9266c58bcdbffd5a40c33835c9dd26", size = 2040175, upload-time = "2025-08-13T01:43:33.762Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ea/c17fb9428c8f0dcc605596f9561627a5b9ef629d356204ee5088cfcf52c6/rustworkx-0.17.1-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b809e0aa2927c68574b196f993233e269980918101b0dd235289c4f3ddb2115", size = 2324771, upload-time = "2025-08-13T01:43:35.553Z" }, + { url = "https://files.pythonhosted.org/packages/d7/40/ec8b3b8b0f8c0b768690c454b8dcc2781b4f2c767f9f1215539c7909e35b/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7e82c46a92fb0fd478b7372e15ca524c287485fdecaed37b8bb68f4df2720f2", size = 2068584, upload-time = "2025-08-13T01:43:37.261Z" }, + { url = "https://files.pythonhosted.org/packages/d9/22/713b900d320d06ce8677e71bba0ec5df0037f1d83270bff5db3b271c10d7/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42170075d8a7319e89ff63062c2f1d1116ced37b6f044f3bf36d10b60a107aa4", size = 2380949, upload-time = "2025-08-13T01:52:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/20/4b/54be84b3b41a19caf0718a2b6bb280dde98c8626c809c969f16aad17458f/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65cba97fa95470239e2d65eb4db1613f78e4396af9f790ff771b0e5476bfd887", size = 2562069, upload-time = "2025-08-13T02:09:27.222Z" }, + { url = "https://files.pythonhosted.org/packages/39/5b/281bb21d091ab4e36cf377088366d55d0875fa2347b3189c580ec62b44c7/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246cc252053f89e36209535b9c58755960197e6ae08d48d3973760141c62ac95", size = 2221186, upload-time = "2025-08-13T01:43:38.598Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/30a941a21b81e9db50c4c3ef8a64c5ee1c8eea3a90506ca0326ce39d021f/rustworkx-0.17.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c10d25e9f0e87d6a273d1ea390b636b4fb3fede2094bf0cb3fe565d696a91b48", size = 2123510, upload-time = "2025-08-13T01:43:40.288Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ef/c9199e4b6336ee5a9f1979c11b5779c5cf9ab6f8386e0b9a96c8ffba7009/rustworkx-0.17.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:48784a673cf8d04f3cd246fa6b53fd1ccc4d83304503463bd561c153517bccc1", size = 2302783, upload-time = "2025-08-13T01:43:42.073Z" }, + { url = "https://files.pythonhosted.org/packages/30/3d/a49ab633e99fca4ccbb9c9f4bd41904186c175ebc25c530435529f71c480/rustworkx-0.17.1-cp39-abi3-win32.whl", hash = "sha256:5dbc567833ff0a8ad4580a4fe4bde92c186d36b4c45fca755fb1792e4fafe9b5", size = 1931541, upload-time = "2025-08-13T01:43:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ec/cee878c1879b91ab8dc7d564535d011307839a2fea79d2a650413edf53be/rustworkx-0.17.1-cp39-abi3-win_amd64.whl", hash = "sha256:d0a48fb62adabd549f9f02927c3a159b51bf654c7388a12fc16d45452d5703ea", size = 2055049, upload-time = "2025-08-13T01:43:44.926Z" }, +] + +[[package]] +name = "ruyaml" +version = "0.91.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distro" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/75/abbc7eab08bad7f47887a0555d3ac9e3947f89d2416678c08e025e449fdc/ruyaml-0.91.0.tar.gz", hash = "sha256:6ce9de9f4d082d696d3bde264664d1bcdca8f5a9dff9d1a1f1a127969ab871ab", size = 239075, upload-time = "2021-12-07T16:19:58.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/9a/16ca152a04b231c179c626de40af1d5d0bc2bc57bc875c397706016ddb2b/ruyaml-0.91.0-py3-none-any.whl", hash = "sha256:50e0ee3389c77ad340e209472e0effd41ae0275246df00cdad0a067532171755", size = 108906, upload-time = "2021-12-07T16:19:56.798Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/ec/7c692cde9125b77e84b307354d4fb705f98b8ccad59a036d5957ca75bfc3/s3transfer-0.17.0.tar.gz", hash = "sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a", size = 155337, upload-time = "2026-04-29T22:07:36.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/72/c6c32d2b657fa3dad1de340254e14390b1e334ce38268b7ad51abda3c8c2/s3transfer-0.17.0-py3-none-any.whl", hash = "sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20", size = 86811, upload-time = "2026-04-29T22:07:34.966Z" }, +] + +[[package]] +name = "schema" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/8da627b65577a8f130fe9dfa88ce94fcb24b1f8b59e0fc763ee61abef8b8/schema-0.7.8.tar.gz", hash = "sha256:e86cc08edd6fe6e2522648f4e47e3a31920a76e82cce8937535422e310862ab5", size = 45540, upload-time = "2025-10-11T13:15:40.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/75/aad85817266ac5285c93391711d231ca63e9ae7d42cd3ca37549e24ebe52/schema-0.7.8-py2.py3-none-any.whl", hash = "sha256:00bd977fadc7d9521bf289850cd8a8aa5f4948f575476b8daaa5c1b57af2dce1", size = 19108, upload-time = "2025-10-11T17:13:07.323Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "selenium" +version = "4.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "trio" }, + { name = "trio-websocket" }, + { name = "typing-extensions" }, + { name = "urllib3", extra = ["socks"] }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/4a/6d0a4f4a07e2a91511a51398203ee82bf6ce644a448aaa35c59b44aa9531/selenium-4.44.0.tar.gz", hash = "sha256:b03a831fcfcab9d912b4682f60718c48a04560d6c62f7496c16b7498c9a4427e", size = 993133, upload-time = "2026-05-12T22:48:19.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/bc/885047e975e996cb317db31c4551caa915aafc6befea990f082c7233adc2/selenium-4.44.0-py3-none-any.whl", hash = "sha256:d01ea3e5ecad8149460a765f7cf5177194c21dcc0173093fc05427c289b1bf24", size = 9654291, upload-time = "2026-05-12T22:48:16.836Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, ] [[package]] -name = "ruyaml" -version = "0.91.0" +name = "semgrep" +version = "1.163.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "distro" }, - { name = "setuptools" }, + { name = "attrs" }, + { name = "boltons" }, + { name = "click" }, + { name = "click-option-group" }, + { name = "colorama" }, + { name = "exceptiongroup" }, + { name = "glom" }, + { name = "jsonschema" }, + { name = "mcp" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation-requests" }, + { name = "opentelemetry-instrumentation-threading" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "peewee" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "rich" }, + { name = "ruamel-yaml" }, + { name = "ruamel-yaml-clib" }, + { name = "semantic-version" }, + { name = "tomli" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/75/abbc7eab08bad7f47887a0555d3ac9e3947f89d2416678c08e025e449fdc/ruyaml-0.91.0.tar.gz", hash = "sha256:6ce9de9f4d082d696d3bde264664d1bcdca8f5a9dff9d1a1f1a127969ab871ab", size = 239075, upload-time = "2021-12-07T16:19:58.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/08/c6f59074bf45951a1bf601da6185f6e16c00a40c12d8843838b04316df59/semgrep-1.163.0.tar.gz", hash = "sha256:42adb798a1850e76dd417e136df1107682dc6eda78e87ea6c8246596b28fe362", size = 55465235, upload-time = "2026-05-13T18:50:48.117Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/9a/16ca152a04b231c179c626de40af1d5d0bc2bc57bc875c397706016ddb2b/ruyaml-0.91.0-py3-none-any.whl", hash = "sha256:50e0ee3389c77ad340e209472e0effd41ae0275246df00cdad0a067532171755", size = 108906, upload-time = "2021-12-07T16:19:56.798Z" }, + { url = "https://files.pythonhosted.org/packages/5c/87/a854fa98d34bb9d865e4d331ba83ffdd7493a03e06b030c79943e8b044a4/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:d689d75e75721d8899e3cfe40d89aef8eaea35c2186d59ca36904f0f429f032d", size = 44797528, upload-time = "2026-05-13T18:50:19.516Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/b9c1d87fd681ae8d26cb508277a27ab4c8f352034b50e60d8da40d487f98/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:6c0c7f89e1638ae94308c6554706c0ec23a2381531f5bb78fa6c2a74de3d315d", size = 48736008, upload-time = "2026-05-13T18:50:23.847Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6d/db05a6113252c93f8d7f1d2cde5762c77c09d8bb2fdcad901d09b0ce55d1/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_35_aarch64.whl", hash = "sha256:2c217f08776793ebf57f233a1285cbc6e8468464e410574d78177f3a3d635473", size = 78151150, upload-time = "2026-05-13T18:50:27.342Z" }, + { url = "https://files.pythonhosted.org/packages/15/c6/757caf53312b5be2e429864d1c9e8ed59fc32106620039beef60c9eb0a74/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_35_x86_64.whl", hash = "sha256:768166fe64a2bf75e1e9f007b75350b136d5e56d87fd322b9fcffe16797c6b21", size = 76384612, upload-time = "2026-05-13T18:50:31.965Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/5ebe97ea3407d1cde383511bca97346c969dba4fb85173533f115bdd7116/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_aarch64.whl", hash = "sha256:3b54184862790b751c85a886d0efba0e5e9b7f7a1521eb60dbd3a244911798e8", size = 78613073, upload-time = "2026-05-13T18:50:36.478Z" }, + { url = "https://files.pythonhosted.org/packages/44/9f/c11c212d69b642d715d121d8f62979fab8f8402425013bb1055bf927a2c9/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_x86_64.whl", hash = "sha256:10240148f690e3ae384e67afa7a51456bcb881781281b02f76379a698ca8bdbc", size = 76131485, upload-time = "2026-05-13T18:50:40.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/641beb66eb01806e1eae4e56ee1e1e4fae205aa63c4c4c0195ee0d6dca1c/semgrep-1.163.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:c268ed2671eb47b33ab4007972365a5a5f8d3c760315ca3850b186936a7b668c", size = 56395339, upload-time = "2026-05-13T18:50:44.262Z" }, ] [[package]] @@ -1722,100 +4212,108 @@ wheels = [ ] [[package]] -name = "super-collections" -version = "0.6.2" +name = "smmap" +version = "5.0.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hjson" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" } + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" }, + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] [[package]] -name = "rustarium" -source = { editable = "." } +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "spdx-tools" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "beartype" }, { name = "click" }, - { name = "loguru" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, + { name = "license-expression" }, + { name = "ply" }, + { name = "pyyaml" }, + { name = "rdflib" }, + { name = "semantic-version" }, + { name = "uritools" }, + { name = "xmltodict" }, ] - -[package.optional-dependencies] -opentelemetry = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-sdk" }, +sdist = { url = "https://files.pythonhosted.org/packages/2f/99/33383f587b59cbd191cc1c0fd5408e550b9275e0090d32561cbddc973fdc/spdx_tools-0.8.5.tar.gz", hash = "sha256:be600beb2f762f0116025e05490d399e724f668bef84025a7c421bb266688bdb", size = 696323, upload-time = "2026-03-13T09:29:23.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/f5/1ac402e1f8d253fa60ac167c4a1fb7eeb0f29a04942788e2d43c984cd749/spdx_tools-0.8.5-py3-none-any.whl", hash = "sha256:7c2d5865941be9d2e898f5b084e8d5422dd298dc5a29320ddb198fec304f59c4", size = 286573, upload-time = "2026-03-13T09:29:20.907Z" }, ] -[package.dev-dependencies] -dev = [ - { name = "mdformat" }, - { name = "mdformat-frontmatter" }, - { name = "mdformat-gfm" }, - { name = "mypy" }, - { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-cov" }, - { name = "pytest-mock" }, - { name = "respx" }, - { name = "ruff" }, - { name = "yamlfix" }, +[[package]] +name = "sse-starlette" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, ] -docs = [ - { name = "mike" }, - { name = "mkdocs" }, - { name = "mkdocs-gen-files" }, - { name = "mkdocs-literate-nav" }, - { name = "mkdocs-macros-plugin" }, - { name = "mkdocs-material" }, - { name = "mkdocs-minify-plugin" }, - { name = "mkdocs-nav-weight" }, - { name = "mkdocs-section-index" }, - { name = "mkdocstrings", extra = ["python"] }, - { name = "pymdown-extensions" }, +sdist = { url = "https://files.pythonhosted.org/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" }, ] -[package.metadata] -requires-dist = [ - { name = "click", specifier = ">=8.0.0" }, - { name = "loguru", specifier = ">=0.7.0" }, - { name = "opentelemetry-api", marker = "extra == 'opentelemetry'", specifier = ">=1.0.0" }, - { name = "opentelemetry-sdk", marker = "extra == 'opentelemetry'", specifier = ">=1.0.0" }, - { name = "pydantic", specifier = ">=2.0.0" }, - { name = "pydantic-settings", specifier = ">=2.0.0" }, +[[package]] +name = "starlette" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] -provides-extras = ["opentelemetry"] -[package.metadata.requires-dev] -dev = [ - { name = "mdformat", specifier = "~=1.0" }, - { name = "mdformat-frontmatter", specifier = "~=2.0" }, - { name = "mdformat-gfm", specifier = "~=1.0" }, - { name = "mypy", specifier = "~=2.1" }, - { name = "pre-commit", specifier = "~=4.1" }, - { name = "pytest", specifier = "~=9.0" }, - { name = "pytest-asyncio", specifier = "~=1.3" }, - { name = "pytest-cov", specifier = "~=7.1" }, - { name = "pytest-mock", specifier = "~=3.15" }, - { name = "respx", specifier = "~=0.23" }, - { name = "ruff", specifier = "~=0.15" }, - { name = "yamlfix", specifier = "~=1.19" }, +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, ] -docs = [ - { name = "mike", specifier = "~=2.2" }, - { name = "mkdocs", specifier = "~=1.6" }, - { name = "mkdocs-gen-files", specifier = "~=0.5" }, - { name = "mkdocs-literate-nav", specifier = "~=0.6" }, - { name = "mkdocs-macros-plugin", specifier = "~=1.5" }, - { name = "mkdocs-material", specifier = "~=9.7" }, - { name = "mkdocs-minify-plugin", specifier = "~=0.8" }, - { name = "mkdocs-nav-weight", specifier = "~=0.3" }, - { name = "mkdocs-section-index", specifier = "~=0.3" }, - { name = "mkdocstrings", extras = ["python"], specifier = "~=1.0" }, - { name = "pymdown-extensions", specifier = "~=10.21" }, + +[[package]] +name = "taplo" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/79/513513960377e1212a28446acb323cf77dfce162e825a822f035b02a422d/taplo-0.9.3.tar.gz", hash = "sha256:6b73b45b9adbd20189d8981ac9055d5465227c58bbe1b0646a7588a1a5c07a1a", size = 102556, upload-time = "2024-08-19T10:22:15.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/42/a93c18ebb7cf3ee2a7a30dd2fda654aca458956c3b64bdfb9d82b2c42679/taplo-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1c3db689406d538420c64aa779ac8694cf44c13a46e158d6df406de65980b9c7", size = 4248497, upload-time = "2024-08-19T10:21:59.954Z" }, + { url = "https://files.pythonhosted.org/packages/82/d2/f5b6e4a4f474f9fe613b5b91012520c3f62e46748a6ce9fd61fc2fb52fa2/taplo-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1e7782f33f97e7aa658d18788748bce5cf3ce440eeb419cf5861cf542740e610", size = 4044421, upload-time = "2024-08-19T10:22:03.06Z" }, + { url = "https://files.pythonhosted.org/packages/7d/32/4ac46ff15bb9d060f50ad31fb3a80aa8ee1e6ca500104ca8569f6fbdee3d/taplo-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29d4d7abfcc10bd536e5a43fe6ec2c1931507c1433e79df03ea22e1030611cb6", size = 4334420, upload-time = "2024-08-19T10:22:05.68Z" }, + { url = "https://files.pythonhosted.org/packages/ef/cc/656aed22a59cf4c50dcaaa66aaa570d4a1412acdd8ea429120a6bb00f336/taplo-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f12648f273478d7330cb3529c82f48f388501e1122e0bea78bce5ff5972b8b", size = 4468935, upload-time = "2024-08-19T10:22:08.106Z" }, + { url = "https://files.pythonhosted.org/packages/21/15/d8db1db6382b444122fa1a66fe5fe0dd5b04bfbe68c74bdb5345aec11eb2/taplo-0.9.3-py3-none-win32.whl", hash = "sha256:9ab7df76a3facc6d0dd2fe2dae3e8eb52fa458d31d27878d5eac14f5cbc0abac", size = 3482612, upload-time = "2024-08-19T10:22:10.904Z" }, + { url = "https://files.pythonhosted.org/packages/42/3c/df6641d7e2e84a6dd4de3b3a4426db7f6a7270c05bbdeadd523645c9c45f/taplo-0.9.3-py3-none-win_amd64.whl", hash = "sha256:7d80b630b93fb43cee99d1e1ee07b616236dc5615efaf7cd51074b4cffc33bab", size = 3985843, upload-time = "2024-08-19T10:22:13.446Z" }, ] [[package]] @@ -1827,6 +4325,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, ] +[[package]] +name = "texttable" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/dc/0aff23d6036a4d3bf4f1d8c8204c5c79c4437e25e0ae94ffe4bbb55ee3c2/texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638", size = 12831, upload-time = "2023-10-03T09:48:12.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917", size = 10768, upload-time = "2023-10-03T09:48:10.434Z" }, +] + [[package]] name = "tomli" version = "2.4.1" @@ -1881,9 +4388,106 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", size = 161875, upload-time = "2026-05-10T07:38:22.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "trio" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/b6/c744031c6f89b18b3f5f4f7338603ab381d740a7f45938c4607b2302481f/trio-0.33.0.tar.gz", hash = "sha256:a29b92b73f09d4b48ed249acd91073281a7f1063f09caba5dc70465b5c7aa970", size = 605109, upload-time = "2026-02-14T18:40:55.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/93/dab25dc87ac48da0fe0f6419e07d0bfd98799bed4e05e7b9e0f85a1a4b4b/trio-0.33.0-py3-none-any.whl", hash = "sha256:3bd5d87f781d9b0192d592aef28691f8951d6c2e41b7e1da4c25cde6c180ae9b", size = 510294, upload-time = "2026-02-14T18:40:53.313Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "outcome" }, + { name = "trio" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" }, +] + +[[package]] +name = "trove-classifiers" +version = "2026.5.20.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/ee/4900b7d32cfe5bf40c31c3e65205922f65c6dceb32fc3f855711639d4025/trove_classifiers-2026.5.20.19.tar.gz", hash = "sha256:6e611993987ca9326968ad70452733dadd31471599d39896045b28970a9bb81e", size = 17036, upload-time = "2026-05-20T19:17:44.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/96/9666c8ba2e7695e804b2b2129e45202320d2a8b5e2c9f6e5e837a61708f5/trove_classifiers-2026.5.20.19-py3-none-any.whl", hash = "sha256:7a173916960d0635fcbf610550d2c27bcc9125164d6f397adf46fc1ef6455c7c", size = 14224, upload-time = "2026-05-20T19:17:42.949Z" }, +] + +[[package]] +name = "ty" +version = "0.0.38" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/3b/45be6b37d5060d6917bf7f1f234c00d360fc5f8b7486f8a96af640e25661/ty-0.0.38.tar.gz", hash = "sha256:fbc8d47f7630457669ab41e333dc093897fdb7ead1ffc94dcf8f30b5d39aa56d", size = 5681218, upload-time = "2026-05-20T00:15:32.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/43/ea9b4e57d6a266670dbe34858e92f6093ca054ad1b48f1c82580a72340fb/ty-0.0.38-py3-none-linux_armv6l.whl", hash = "sha256:3501dcf44ca03f813f9cb4fabfdf601adc0ac1337c411405b470530679e37a45", size = 11289326, upload-time = "2026-05-20T00:14:52.371Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ff/24e2f623a1c6b5f5ccf8bf82fccd937033c6a7dba57a4028c7f41270fa4a/ty-0.0.38-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b34b4094b76252c3e8c90762cdd5e8a9f1101534484745ff4b480f71eb38ac2e", size = 11063047, upload-time = "2026-05-20T00:14:42.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/41/4f0d910f0acbd20b358eda80a5cd6a8361d27ff5b8e87ab559d3f69f125e/ty-0.0.38-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c518ad33a877677365baab2e21d82cf59ffee789203a15a143f5179ee5a1d3f8", size = 10494436, upload-time = "2026-05-20T00:15:24.425Z" }, + { url = "https://files.pythonhosted.org/packages/69/d8/da06833422082aa98b169a391f9197e2d73865e96c90b6979ac886b890a2/ty-0.0.38-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9238494722303eccddc6a27eb647948b694eecd6b974910d13b9e6cd46bbeb6a", size = 11000992, upload-time = "2026-05-20T00:14:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/16/f7/e1172197fb827e6410ca3eb0dc68ef2789f3c70683696f2a0ce5c90764fd/ty-0.0.38-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d91d7336c5d51bf822ac0df512f300584ca4dcca041fc6a6d7df03a8ddbb31", size = 11058583, upload-time = "2026-05-20T00:15:11.314Z" }, + { url = "https://files.pythonhosted.org/packages/5b/61/7fbaf0c05981e006a8804287819c574dff90a6bf8e96efad7226be0700aa/ty-0.0.38-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65165879814993450710b9349791e4898c65e36b1e14eec554884c06a2f20ff1", size = 11531036, upload-time = "2026-05-20T00:15:14.62Z" }, + { url = "https://files.pythonhosted.org/packages/49/e3/47c0c64e401d50f925df3e52479d4e7626754b2a9e38201d142fdacd6252/ty-0.0.38-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d61868b8d1c4033bf8088191de953fed245c2f9e1bb9d2d53e5699170b0924c", size = 12129991, upload-time = "2026-05-20T00:14:39.475Z" }, + { url = "https://files.pythonhosted.org/packages/90/99/2f452d02901bcd7f1b109cf5b848727ce37f372c3406143aa52d1305d40e/ty-0.0.38-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f9a9175548c98dbff7707865738c07c2b1f8e07a09b8c68101baebb5dac59a4", size = 11756167, upload-time = "2026-05-20T00:15:27.526Z" }, + { url = "https://files.pythonhosted.org/packages/dd/0c/c7e14d111c813e1a20b82e944f1c997c4631a2bb710eaa64fb6b26835e13/ty-0.0.38-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375d3a964c6b4aea2e9237fdb5eb9ed03dc43088986a94209a28a4ea3b62001c", size = 11637099, upload-time = "2026-05-20T00:15:21.261Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/ab02659dd1ed62898db7db4d37f9937c80854dd45e95093fa0fe10328d82/ty-0.0.38-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:cdfd547782c45267aa0b52abad31bd406bf4768c264532ef9e2360cd3c6ce048", size = 11813583, upload-time = "2026-05-20T00:14:45.875Z" }, + { url = "https://files.pythonhosted.org/packages/7e/57/bd1b5ebf4e71a4295484afac0202df1740b0807762b86744b1bef4534984/ty-0.0.38-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:858bc675b75626470abe4e6c3b3934b853642b04f2ac4d7139fcefea3b48b213", size = 10975405, upload-time = "2026-05-20T00:15:30.354Z" }, + { url = "https://files.pythonhosted.org/packages/e7/55/0305c78711bbd23922cf291996a08ef9544f4179da98e9a75c14e608f379/ty-0.0.38-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:54be4f00432870da42cd74fe145a3362fd248e22d032c74bd807cb45bf068f94", size = 11097551, upload-time = "2026-05-20T00:14:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4f/7effe7f9a6ac9719eb7234172c01739c5f888bb47f9acc2ea8da1f4afed3/ty-0.0.38-py3-none-musllinux_1_2_i686.whl", hash = "sha256:494af66a76a86dbf16a3003d3b63b03484aa4c7489dfe11f3ee5413b98b22d60", size = 11214391, upload-time = "2026-05-20T00:15:18.094Z" }, + { url = "https://files.pythonhosted.org/packages/75/cd/d9fdfec3a74a6ad0209fa5e7113ae29d4f457d0651cfbb813b4c6563e0d4/ty-0.0.38-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3d92527c4be78a5ce6d32e8bb0aa2a6988d4076eddf1294e56fdaf06d1a98e7e", size = 11730871, upload-time = "2026-05-20T00:14:49.219Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4a/beefade12d109b4f7793d61b04b4478b1ad4d1465a719e7ff55b2d42461a/ty-0.0.38-py3-none-win32.whl", hash = "sha256:36fc5dd5dc09207ff3004b1560a79a3fb8d12456daeec914a7b802a918da654c", size = 10548583, upload-time = "2026-05-20T00:15:07.892Z" }, + { url = "https://files.pythonhosted.org/packages/15/64/941b205e2e46cc2297c245c64aa7691410b7454fa4d07a6cb3cf59487833/ty-0.0.38-py3-none-win_amd64.whl", hash = "sha256:eef0a8956ba14514076b1a963d13eb32986d9ebad7f0527b3cc01cb68bf35147", size = 11650542, upload-time = "2026-05-20T00:15:01.441Z" }, + { url = "https://files.pythonhosted.org/packages/59/02/c1c4f9ec4b94d95190636fa13f79c32f65165fbe3a0503882d4df164d2ac/ty-0.0.38-py3-none-win_arm64.whl", hash = "sha256:79abfc8658a026c30b1c955613437dab3ef4b12feca56a3e6df50903cc39e07f", size = 11010307, upload-time = "2026-05-20T00:15:04.567Z" }, +] + [[package]] name = "typer" -version = "0.25.1" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -1891,9 +4495,9 @@ dependencies = [ { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/07/b822e1b307d40e263e8253d2384cf98c51aa2368cc7ba9a07e523a1d964b/typer-0.23.1.tar.gz", hash = "sha256:2070374e4d31c83e7b61362fd859aa683576432fd5b026b060ad6b4cd3b86134", size = 120047, upload-time = "2026-02-13T10:04:30.984Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/9b286ab899c008c2cb05e8be99814807e7fbbd33f0c0c960470826e5ac82/typer-0.23.1-py3-none-any.whl", hash = "sha256:3291ad0d3c701cbf522012faccfbb29352ff16ad262db2139e6b01f15781f14e", size = 56813, upload-time = "2026-02-13T10:04:32.008Z" }, ] [[package]] @@ -1917,6 +4521,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "unidiff" +version = "0.7.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/48/81be0ac96e423a877754153699731ef439fd7b80b4c8b5425c94ed079ebd/unidiff-0.7.5.tar.gz", hash = "sha256:2e5f0162052248946b9f0970a40e9e124236bf86c82b70821143a6fc1dea2574", size = 20931, upload-time = "2023-03-10T01:05:39.185Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/54/57c411a6e8f7bd7848c8b66e4dcaffa586bf4c02e63f2280db0327a4e6eb/unidiff-0.7.5-py2.py3-none-any.whl", hash = "sha256:c93bf2265cc1ba2a520e415ab05da587370bc2a3ae9e0414329f54f0c2fc09e8", size = 14386, upload-time = "2023-03-10T01:05:36.594Z" }, +] + +[[package]] +name = "update-checker" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/0b/1bec4a6cc60d33ce93d11a7bcf1aeffc7ad0aa114986073411be31395c6f/update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13", size = 6699, upload-time = "2020-08-04T07:08:50.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd", size = 7008, upload-time = "2020-08-04T07:08:49.51Z" }, +] + +[[package]] +name = "uritools" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/76/034508ab9280225ef1352f7916fef0fb126e5e18dd66069f44f3c1336533/uritools-6.1.1.tar.gz", hash = "sha256:579b75d9438431574df07746a3c1445991d07f4bae65b25cdc0f43967670fb61", size = 30636, upload-time = "2026-05-18T18:22:43.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/ee/e229501e079194baea67362b51790a4a0c36a86ce1124d1815a439780251/uritools-6.1.1-py3-none-any.whl", hash = "sha256:c390cf204db7f14637bb47a2a8f2cc0a813e0bc3d16b6bc58e87433fa76e875a", size = 11991, upload-time = "2026-05-18T18:22:41.326Z" }, +] + +[[package]] +name = "urlchecker" +version = "0.0.35" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fake-useragent" }, + { name = "requests" }, + { name = "selenium" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/11/b87b3e014d93bfb7dc288fe907fefdf401bf2a35494cdaef9438d2e701e5/urlchecker-0.0.35.tar.gz", hash = "sha256:e303c4d240f3e00e21583bf9636e6792b4d7abd889de5b3114e78662cd2b7f45", size = 90890, upload-time = "2024-02-03T22:48:14.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/7e/ef9950a258bcbb5d2770aef1bd681a471d12b871d55f1f6883c8ac41514d/urlchecker-0.0.35-py3-none-any.whl", hash = "sha256:a6297b4627ead89a71dcc1c03a366fd9380640bf2a02d7135edcd48ff7a44290", size = 97388, upload-time = "2024-02-03T22:48:12.254Z" }, +] + [[package]] name = "urllib3" version = "2.7.0" @@ -1926,13 +4574,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + [[package]] -name = "verspec" -version = "0.1.0" +name = "userpath" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/44/8126f9f0c44319b2efc65feaad589cadef4d77ece200ae3c9133d58464d0/verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e", size = 27123, upload-time = "2020-11-30T02:24:09.646Z" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/b7/30753098208505d7ff9be5b3a32112fb8a4cb3ddfccbbb7ba9973f2e29ff/userpath-1.9.2.tar.gz", hash = "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815", size = 11140, upload-time = "2024-02-29T21:39:08.742Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl", hash = "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d", size = 9065, upload-time = "2024-02-29T21:39:07.551Z" }, +] + +[[package]] +name = "uv" +version = "0.11.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/609d5d01ba21dc8f0974610ca7802fbb2c946a0c38665cfe5c5aeddbefb5/uv-0.11.15.tar.gz", hash = "sha256:755f959ec6a2fd8ccb6ee76ad90ab759d2eb1f4797444078645dd1ee4bca92d6", size = 4159545, upload-time = "2026-05-18T19:57:48.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/7c/dcc230c5911884d8848145dabcac8fb95a5ed6f9fe1c57fae8242618f28a/uv-0.11.15-py3-none-linux_armv6l.whl", hash = "sha256:83b04ab49514a0a761ffedb36a748ee81f87746671e72088e5f32c9585e5f1a9", size = 23110183, upload-time = "2026-05-18T19:57:23.051Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/efd4e044b60eb9c3c12ee386be098d56c335538ccec7caa49349cfba9344/uv-0.11.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cae61f737be075b90be9e3f07d961072aed7019f4c9b8ed5c5d41c4d6cade3", size = 22637941, upload-time = "2026-05-18T19:57:26.752Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b8/48627f895a1569e576822e0a8416aa4797eb4a4551de21a4ad97b9b5819d/uv-0.11.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9accae33619a9166e5c48531deb455d672cfb89f9357a00975e669c76b0bd49f", size = 21258803, upload-time = "2026-05-18T19:57:05.473Z" }, + { url = "https://files.pythonhosted.org/packages/af/50/4bc8a148274feabee2d9c9f1fa15009e10c0228dfe57981ee3ea2ef1d481/uv-0.11.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c0cf52cd6d50bb9e05e2d968f45f80761107e4cbc8d4a26d9758f9d8274aaec1", size = 23066178, upload-time = "2026-05-18T19:57:33.058Z" }, + { url = "https://files.pythonhosted.org/packages/a9/56/139fc3bec9a8b0a25bfe2196123adb9f16124da437bf4fbcf0d21cfcafb2/uv-0.11.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:49dc6ed70bff00937384f96cdc4b1a4742d18e5504ec2c4a1214dba2dee5687a", size = 22705332, upload-time = "2026-05-18T19:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b0/b18b3dd204f8c213236a1ebd148e009861637129a8cce34df0e9aa22ed40/uv-0.11.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adb9a89352539fdd8f7cd5f9966cf9f94fc5b98e0ccdf5003a04123dc6423bec", size = 22707534, upload-time = "2026-05-18T19:58:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/3ca09f95572df99d361b49c96b1297149e96e120d8d1ecf074095a4b6da4/uv-0.11.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40ff67e3f8e8a7533781a2e892a534975a93acb83ea35460e64e7b2bf2111774", size = 24096607, upload-time = "2026-05-18T19:58:11.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/be/3bdee21a296bbf5336a526e3613d0e7d4538dacc39c62d7fcba55d15f6b0/uv-0.11.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6463a299ed7e6b5a800ed6f108af8e1588352629424133ddef7572b0e1e1118", size = 25082562, upload-time = "2026-05-18T19:57:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/73/f371f3689ffe741066468d001d85f739fc4b5574de83b639ef19b5e8a7f4/uv-0.11.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68c1e62d4b78578b90b833553286b65d6a7e327537716441068583ba652ec4f5", size = 24253391, upload-time = "2026-05-18T19:57:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/d3/16/fe392d618af6b00c064b3e718d585dcf791546a77c5123a5bec07ce53a0a/uv-0.11.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98edf1bdaf82447014852051d93e3ee95012509c567bf057fd117e6bdbd9a807", size = 24415871, upload-time = "2026-05-18T19:58:19.651Z" }, + { url = "https://files.pythonhosted.org/packages/6e/24/2e92a052fb6334fcd746d1c7cb57847c204b118c84f5da53c0f9e129f7b7/uv-0.11.15-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:be8f76d25bcf4c92bb384240ac1bf9aa7f51063d0bdeca4c9cf0ec3ed8b145e0", size = 23159007, upload-time = "2026-05-18T19:57:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2e/6923d0658d164bb2c435ed1868aa2d49b3074594679917a001ff92dc95bb/uv-0.11.15-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:f9f4fbbf4fe485522054f3c7496c6e8e932d6436e4200ff3daf718db0b7c7bd5", size = 23769385, upload-time = "2026-05-18T19:58:15.856Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/7e34cd949e57360814e8064cc9fb7104df445d0f6a663504e5f7473480aa/uv-0.11.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0ed920e896b2fd13a35031707e307e42fbb2681458b967440a17272d86d49137", size = 23860973, upload-time = "2026-05-18T19:57:55.575Z" }, + { url = "https://files.pythonhosted.org/packages/28/98/8fe1f5f9d816e94569a0298dd8e0936801097625fa1952162951f0d628b6/uv-0.11.15-py3-none-musllinux_1_1_i686.whl", hash = "sha256:41d907611f3e6a13262807fd7f0a17849f76285ca80f536f6b3943732bdc6656", size = 23431392, upload-time = "2026-05-18T19:57:59.814Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6b/76a1ce2fa860026913a5941700cdc7d715fce9c3277a3fa3489cf2523ca0/uv-0.11.15-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:e3b68f8bf1a4568710f77e5bda9182ce7682811d89a8e7468c22460e032b234d", size = 24519478, upload-time = "2026-05-18T19:57:51.165Z" }, + { url = "https://files.pythonhosted.org/packages/43/60/1d58e8a05718cb50494763115710b73846cacb651fd735d285233fd72c59/uv-0.11.15-py3-none-win32.whl", hash = "sha256:8e2da3076761086a5b76869c3f38ef0509c836046ef41ddd19485dfd7271dca9", size = 22020178, upload-time = "2026-05-18T19:58:07.64Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/40fcefcb348af660488597ed3c01363df7344e60611f8883750dc596f5c6/uv-0.11.15-py3-none-win_amd64.whl", hash = "sha256:cc3915ab291a1ecaf31de05f5d8bd70d09c66fe9911a53f70d9efa62ff0dbd8a", size = 24668779, upload-time = "2026-05-18T19:57:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7d/fa3a9960c95af9bbe2a629048760d0b9b4fead8ccd4f2235af747ec7cdf0/uv-0.11.15-py3-none-win_arm64.whl", hash = "sha256:4f39426a13dee24897aed60c4b98058c66f18bd983885ac5f4a54a04b24fbddf", size = 23198178, upload-time = "2026-05-18T19:57:14.68Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/b1/8e7077a8641086aea449e1b5752a570f1b5906c64e0a33cd6d93b63a066b/uvicorn-0.47.0.tar.gz", hash = "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533", size = 90582, upload-time = "2026-05-14T18:16:54.455Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31", size = 19640, upload-time = "2020-11-30T02:24:08.387Z" }, + { url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" }, ] [[package]] @@ -1983,6 +4679,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] +[[package]] +name = "wcmatch" +version = "8.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/c4/55e0d36da61d7b8b2a49fd273e6b296fd5e8471c72ebbe438635d1af3968/wcmatch-8.5.2.tar.gz", hash = "sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2", size = 114983, upload-time = "2024-05-15T12:51:08.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/78/533ef890536e5ba0fd4f7df37482b5800ecaaceae9afc30978a1a7f88ff1/wcmatch-8.5.2-py3-none-any.whl", hash = "sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478", size = 39397, upload-time = "2024-05-15T12:51:06.2Z" }, +] + [[package]] name = "wcwidth" version = "0.7.0" @@ -1992,6 +4700,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, ] +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + [[package]] name = "win32-setctime" version = "1.2.0" @@ -2001,6 +4718,96 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, ] +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, + { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, + { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, + { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "wsproto" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, +] + +[[package]] +name = "xmltodict" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, +] + [[package]] name = "yamlfix" version = "1.19.1" @@ -2016,11 +4823,170 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/c7/cba5941b7066f59dbddfe88bdc7154edbe5119bacb3814599997fbc2acac/yamlfix-1.19.1-py3-none-any.whl", hash = "sha256:b885fcf171a2eb59df83c219355bb17dd147675645e2756754372c0bd0b80ea5", size = 28393, upload-time = "2025-12-18T09:57:21.547Z" }, ] +[[package]] +name = "yamllint" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathspec" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/a0/8fc2d68e132cf918f18273fdc8a1b8432b60d75ac12fdae4b0ef5c9d2e8d/yamllint-1.38.0.tar.gz", hash = "sha256:09e5f29531daab93366bb061e76019d5e91691ef0a40328f04c927387d1d364d", size = 142446, upload-time = "2026-01-13T07:47:53.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/92/aed08e68de6e6a3d7c2328ce7388072cd6affc26e2917197430b646aed02/yamllint-1.38.0-py3-none-any.whl", hash = "sha256:fc394a5b3be980a4062607b8fdddc0843f4fa394152b6da21722f5d59013c220", size = 68940, upload-time = "2026-01-13T07:47:51.343Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/df/f1c7a3de0831cd83194f1a85c5bb431b13f81e6b45079314c86d1c4ef3f2/yarl-1.24.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5249a113065c2b7a958bc699759e359cd61cfc81e3069662208f48f191b7ed12", size = 129057, upload-time = "2026-05-19T21:27:47.564Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/7daafb32dd7562bf45b1ce56562e7e1a9146f6479b6456873eb8a3413c40/yarl-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4425fa244fbf530b006d0c5f79ce920114cfff5b4f5f6056e669f8e160fdc0", size = 91545, upload-time = "2026-05-19T21:27:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/a8/8f/7b3ec212f1ea0683f55f978e3246bc313c38818664edfc97a9f349a4901e/yarl-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15c0b5e49d3c44e2a0b93e6a49476c5edad0a7686b92c395765a7ea775572a75", size = 91380, upload-time = "2026-05-19T21:27:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1b/8bafab7db23b0567ae9db749099b329d91e3b82bc6028b2050ba583e116c/yarl-1.24.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:246d32a53a947c8f0189f5d699cbd4c7036de45d9359e13ba238d1239678c727", size = 105957, upload-time = "2026-05-19T21:27:53.98Z" }, + { url = "https://files.pythonhosted.org/packages/7f/77/21030c2f8d21d21559719beafc772ada2014be933418ed1eaed9cc800e42/yarl-1.24.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:64480fb3e4d4ed9ed71c48a91a477384fc342a50ca30071d2f8a88d51d9c9413", size = 97242, upload-time = "2026-05-19T21:27:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/f9ea63d1b6aa910a866e089d871fff6cbd49caab29b86b35221a62dfa0d5/yarl-1.24.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:349de4701dc3760b6e876628423a8f147ef4f5599d10aba1e10702075d424ed9", size = 114719, upload-time = "2026-05-19T21:27:58.037Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/04e0ee98ac58a249ea7ed75223f5f901ba81a834f0b4921b58e5cec11757/yarl-1.24.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d162677af8d5d3d6ebab8394b021f4d041ac107a4b705873148a77a49dc9e1b2", size = 112140, upload-time = "2026-05-19T21:27:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/ad/0b9cc9f38a7324a7eb1d80f834eaa5283d17e9271bbda3186e598dddaeac/yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5f5c6ec23a9043f2d139cc072f53dd23168d202a334b9b2fda8de4c3e890d90", size = 106721, upload-time = "2026-05-19T21:28:02.586Z" }, + { url = "https://files.pythonhosted.org/packages/65/e7/a52478ebfc66ec989e085c6ae038b9f1bfa4190baa193b133b669c709e2f/yarl-1.24.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:60de6742447fbbf697f16f070b8a443f1b5fe6ca3826fbef9fe70ecd5328e643", size = 106478, upload-time = "2026-05-19T21:28:04.523Z" }, + { url = "https://files.pythonhosted.org/packages/04/d8/5508530fea8472542de00013ae280765fc938ee196fc4030c43a498afb36/yarl-1.24.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acf93187c3710e422368eb768aee98db551ec7c85adc250207a95c16548ab7ac", size = 105423, upload-time = "2026-05-19T21:28:06.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/f1/ece28505e9628e8b756e11bb4f28864a17cc33b6b44db4d2aaf0622bf630/yarl-1.24.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f4b0352fd41fd34b6651934606268816afd6914d09626f9bcbbf018edb0afb3f", size = 99878, upload-time = "2026-05-19T21:28:08.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/fb5d34529b46dd84013afcfb30b8d2bc2832ed03d412736f577d604fa393/yarl-1.24.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6b208bb939099b4b297438da4e9b25357f0b1c791888669b963e45b203ea9f36", size = 114025, upload-time = "2026-05-19T21:28:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/ff9d31aaab024f7a251c0ed308a98ae29bf9f7dc344e78f28b1322431ca2/yarl-1.24.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4b85b8825e631295ff4bc8943f7471d54c533a9360bbe15ebb38e018b555bb8a", size = 105613, upload-time = "2026-05-19T21:28:12.784Z" }, + { url = "https://files.pythonhosted.org/packages/31/7d/3296fb3f3ecd52bf9ae6c16b0895c1cda7e9170a2083861552b683f70264/yarl-1.24.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e26acf20c26cb4fefc631fdb75aca2a6b8fa8b7b5d7f204fb6a8f1e63c706f53", size = 111665, upload-time = "2026-05-19T21:28:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/77aa6ddaca4fbf42e45e675a465c43956dd40702281049975a2aa04eae59/yarl-1.24.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:819ca24f8eafcfb683c1bd5f44f2f488cea1274eb8944731ffd2e1f10f619342", size = 106914, upload-time = "2026-05-19T21:28:15.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/02/7611f22cd1d4ed7373eb7f9ee21fde1046edba2e7c0e514880d760352f48/yarl-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:5cb0f995a901c36be096ccbf4c673591c2faabbe96279598ffaec8c030f85bf4", size = 92658, upload-time = "2026-05-19T21:28:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/91/00/671d0add79938127292839ae44506ce2f7fe8909c72d5a931864f128fd0b/yarl-1.24.2-cp310-cp310-win_arm64.whl", hash = "sha256:f408eace7e22a68b467a0562e0d27d322f91fe3eaaa6f466b962c6cfaea9fa39", size = 87887, upload-time = "2026-05-19T21:28:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" }, + { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" }, + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, +] + +[[package]] +name = "zensical" +version = "0.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/85/ec45162e7824a8f879d887ef0774ee65926bf7d1064e2eebccc7eaee3378/zensical-0.0.43.tar.gz", hash = "sha256:dc2d3804ff562795c1024130e0c3ce79736467930729dda314f096d0e35b98c8", size = 3932396, upload-time = "2026-05-19T09:44:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/c2/55e0709607ae41c266987c3b91a1a9702b37fbbef0d07eddfe5e25c2d823/zensical-0.0.43-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:17c335362b6bac3a50178181694a964f6d9f0c516fc532129ba5a0a5c4103fb6", size = 12706531, upload-time = "2026-05-19T09:43:32.729Z" }, + { url = "https://files.pythonhosted.org/packages/2c/64/ce8627bc5ea30556162b29b041fe97d6a6aef2a87b51f12def628e4fa608/zensical-0.0.43-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8fe97f185194215f6193af45a17d2b30ebd72c8113e3650f2d7d6767b9c2206", size = 12563012, upload-time = "2026-05-19T09:43:35.962Z" }, + { url = "https://files.pythonhosted.org/packages/66/d1/533bc9454f0e06b3d9d8bd2e7ac405308c3d4dee6572acab98f0ed6d1c07/zensical-0.0.43-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c4c85978c765b3e7f347e8102dfe1373d4bbe4229d7008b6bdbf352f1fbcd7f", size = 12947599, upload-time = "2026-05-19T09:43:38.754Z" }, + { url = "https://files.pythonhosted.org/packages/75/a0/94f47d6fb592997be7ab9526938c929f0199adf2637c3c2b2b9b2101b28e/zensical-0.0.43-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90d7c06ffd07b2bdf78bef041d541baba8a3ea51fd2dd84dbdbc5b0229076524", size = 12904911, upload-time = "2026-05-19T09:43:42.434Z" }, + { url = "https://files.pythonhosted.org/packages/96/fb/1db3ad9a86ff772f74a8bc60ad5b447aa02a158e70f94adacf50bdd5c40f/zensical-0.0.43-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60022f4a6b95e46ec0023f51052fcd491743b3ebd08c0066b22a5cf1e741fecd", size = 13269386, upload-time = "2026-05-19T09:43:45.387Z" }, + { url = "https://files.pythonhosted.org/packages/31/ee/b24fd0f94885519d851c35615b086d069a1077b0198021a56755395a4633/zensical-0.0.43-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e278eb948a0b7545d50609d713c7c27e366dade4523ff73a311a5d5f136518a", size = 12999364, upload-time = "2026-05-19T09:43:48.549Z" }, + { url = "https://files.pythonhosted.org/packages/28/78/401ccd7afd9d2690f81b5319b7f1eed05108154ce20e4207053914518c1c/zensical-0.0.43-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b85e5ab99fbda13823e67c43a4be6e5ebda6600602969c6575e143f20ac203fd", size = 13124392, upload-time = "2026-05-19T09:43:50.965Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/9af6eba5826b0ef143fc8308bd1e219e221441e307a958e39f824ba9ab53/zensical-0.0.43-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:751385accc92cccfd4560dabed7c423870686ef6ede244a67e5c96286af25e8f", size = 13177538, upload-time = "2026-05-19T09:43:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/be/6b/cd090bd6659d32692487206469988ee84d41aa6de4cdf9e380f847da90e2/zensical-0.0.43-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:dd3ff5bfa6e65cf3d2550dc639c3da2a3bfa11087b83d57e06623c4c1607d583", size = 13327086, upload-time = "2026-05-19T09:43:56.8Z" }, + { url = "https://files.pythonhosted.org/packages/79/5b/ac2555354b5a53cb9c2c942811905c47be0b9f5603d3c1328ee8564333eb/zensical-0.0.43-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85055a115b12f49c6ab194dcf04f966fc06b690ed6a8ddddd819929fc5f340e6", size = 13284645, upload-time = "2026-05-19T09:43:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c6/1688ec6e5be15e3ab367d7804753291bfbdff3109b06e20c19ce30a7129c/zensical-0.0.43-cp310-abi3-win32.whl", hash = "sha256:8a75ddd4bb3cd3c4a8e71d2ebae44c5611fd636c1d355c6124dd96e2f9c52838", size = 12256740, upload-time = "2026-05-19T09:44:02.102Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a8/d967e70eac810a7e9eb8c5150d6d02848a1f42260f42977c71debed3cb02/zensical-0.0.43-cp310-abi3-win_amd64.whl", hash = "sha256:03a9d1744a6394ad66c355d6f1de04cfd92efa525b0b94bf6dbf6971c5cd2c6b", size = 12496166, upload-time = "2026-05-19T09:44:04.915Z" }, +] + [[package]] name = "zipp" -version = "3.23.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, ] diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 0000000..e93b0fa --- /dev/null +++ b/zensical.toml @@ -0,0 +1,39 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[project] +site_name = "rustarium" +site_url = "https://markurtz.github.io/rustarium/" +site_description = "A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust." +site_author = "markurtz" +repo_url = "https://github.com/markurtz/rustarium" +repo_name = "markurtz/rustarium" +nav = [ + "index.md", + {"Getting Started" = ["getting-started/index.md", "getting-started/installation.md", "getting-started/quickstart.md"]}, + {Guides = ["guides/index.md", "guides/github-workflows.md"]}, + {Examples = ["examples/index.md"]}, + {Reference = ["reference/index.md", "reference/cli.md", "reference/python_api.md", "reference/rust_api.md", "reference/python_coverage.md", "reference/rust_coverage.md"]}, + {Community = ["community/index.md", "community/agents.md", "community/code-of-conduct.md", "community/contributing.md", "community/developing.md", "community/security.md", "community/support.md"]}, +] + +[project.theme] +variant = "modern" +features = ["navigation.indexes"] + +[project.plugins.macros] +module_name = "docs/scripts/macros" + +[project.plugins.mkdocstrings] +handlers = { python = { paths = ["src"] } } From de0de33280427048a0b92d3289bfd1f5549f685f Mon Sep 17 00:00:00 2001 From: Mark Kurtz Date: Fri, 29 May 2026 18:57:33 -0400 Subject: [PATCH 2/2] Updates to rustarium project based on new standards from GitVersioned --- .devcontainer/Dockerfile | 16 +- .github/CODEOWNERS | 30 +- .github/ISSUE_TEMPLATE/bug_report.yml | 13 + .github/ISSUE_TEMPLATE/feature_request.yml | 13 + .github/PULL_REQUEST_TEMPLATE.md | 18 +- .github/actions/oci/build/action.yml | 62 +- .github/actions/oci/publish/action.yml | 51 +- .github/actions/oci/quality/action.yml | 60 +- .github/actions/oci/security/action.yml | 48 - .github/actions/oci/tests/action.yml | 82 +- .github/actions/project/docs/action.yml | 235 +- .github/actions/project/quality/action.yml | 41 +- .github/actions/project/security/action.yml | 50 - .github/actions/project/tests/action.yml | 97 +- .github/actions/python/build/action.yml | 59 +- .github/actions/python/publish/action.yml | 35 +- .github/actions/python/quality/action.yml | 49 +- .github/actions/python/security/action.yml | 43 - .github/actions/python/tests/action.yml | 151 +- .github/actions/rust/build/action.yml | 43 - .github/actions/rust/quality/action.yml | 35 +- .github/actions/rust/security/action.yml | 29 +- .github/actions/rust/tests/action.yml | 138 +- .../actions/utility/comment-upsert/action.yml | 74 + .github/actions/utility/setup-git/action.yml | 39 + .github/actions/utility/setup-oci/action.yml | 58 + .github/dependabot.yml | 20 +- .github/paths-filter.yml | 165 +- .github/pipeline-config.json | 14 + .github/workflows/pipeline-development.yml | 374 ++- .github/workflows/pipeline-main.yml | 402 ++- .github/workflows/pipeline-nightly.yml | 506 +++- .github/workflows/pipeline-release.yml | 459 ++- .github/workflows/pipeline-weekly.yml | 417 ++- .github/workflows/util-cleanup.yml | 133 + .../workflows/util-development-cleanup.yml | 48 - .github/workflows/util-pr-comment.yml | 225 +- .gitignore | 20 + AGENTS.md | 157 +- CITATION.cff | 22 +- CLAUDE.md | 17 + CODE_OF_CONDUCT.md | 21 +- CONTRIBUTING.md | 25 +- Cargo.lock | 2 +- Cargo.toml | 27 +- DEVELOPING.md | 212 +- Dockerfile | 4 +- README.md | 16 + SECURITY.md | 42 +- SUPPORT.md | 24 +- crates/rustarium-core/src/lib.rs | 14 - crates/rustarium-core/src/sum.rs | 14 - .../rustarium-core/tests/test_integration.rs | 14 - cst.yaml | 24 + docs/guides/github-workflows.md | 62 +- docs/index.md | 39 +- docs/reference/index.md | 2 +- docs/reference/python_api.md | 5 - docs/scripts/gen_ref_pages.py | 155 + docs/scripts/macros.py | 14 - examples/README.md | 33 +- examples/__init__.py | 17 + .../action.yml => examples/conftest.py | 31 +- examples/example_template/test_example.py | 15 + llms-full.txt | 2654 +++++++++++++++-- llms.txt | 41 +- pyproject.toml | 166 +- scripts/README.md | 33 +- scripts/__init__.py | 17 + scripts/check_links.py | 16 +- scripts/generate_doc_tests.py | 16 - scripts/run_oci.py | 20 +- src/rustarium/__init__.py | 16 + src/rustarium/__main__.py | 40 +- src/rustarium/compat.py | 14 + src/rustarium/logging.py | 243 +- src/rustarium/settings.py | 55 + .../publish/action.yml => tests/__init__.py | 15 +- tests/conftest.py | 303 ++ tests/python/__init__.py | 1 + tests/python/integration/test_integration.py | 28 +- tests/python/unit/test_logging.py | 196 +- tests/python/unit/test_settings.py | 46 +- uv.lock | 165 +- zensical.toml | 25 +- 85 files changed, 7050 insertions(+), 2420 deletions(-) delete mode 100644 .github/actions/oci/security/action.yml delete mode 100644 .github/actions/project/security/action.yml delete mode 100644 .github/actions/python/security/action.yml delete mode 100644 .github/actions/rust/build/action.yml create mode 100644 .github/actions/utility/comment-upsert/action.yml create mode 100644 .github/actions/utility/setup-git/action.yml create mode 100644 .github/actions/utility/setup-oci/action.yml create mode 100644 .github/pipeline-config.json create mode 100644 .github/workflows/util-cleanup.yml delete mode 100644 .github/workflows/util-development-cleanup.yml create mode 100644 CLAUDE.md delete mode 100644 docs/reference/python_api.md create mode 100644 docs/scripts/gen_ref_pages.py rename .github/actions/rust/publish/action.yml => examples/conftest.py (56%) create mode 100644 examples/example_template/test_example.py rename .github/actions/project/publish/action.yml => tests/__init__.py (64%) create mode 100644 tests/conftest.py create mode 100644 tests/python/__init__.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9d31a21..dc762b9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -31,15 +31,17 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ # Install hatch globally via uv RUN uv tool install hatch --to /usr/local/bin -# Switch to vscode user to install Rust toolchain -USER vscode -ENV HOME=/home/vscode -ENV PATH=$HOME/.cargo/bin:$PATH +# Install Rust toolchain globally (migrated to standard pathways) +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH -# Install Rust stable and cargo utilities -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -# Ensure cargo-nextest, maturin, and other essentials can be installed simply +# Ensure cargo-nextest, maturin, and other essentials are installed globally RUN cargo install maturin cargo-nextest +# Ensure the cargo home directory is writable by vscode user +RUN chmod -R a+w /usr/local/cargo + ENV SHELL=/bin/bash diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0f0af36..cec20ca 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,24 +1,17 @@ -# ============================================================================= -# CODEOWNERS — Automated Review Assignment +# Copyright 2026 markurtz # -# GitHub uses this file to automatically request reviews from the designated -# code owners when a pull request modifies files matching a given pattern. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# HOW TO USE THIS FILE: -# 1. Replace all placeholder GitHub usernames (e.g., @org/team-name or -# @username) with your actual GitHub team or individual usernames. -# 2. Rules are evaluated from top to bottom. The LAST matching rule wins. -# 3. Patterns follow the same syntax as .gitignore. +# http://www.apache.org/licenses/LICENSE-2.0 # -# REFERENCE: -# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# ============================================================================= +# 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. -# ----------------------------------------------------------------------------- -# Default Owner -# Catches all files not matched by a more specific rule below. -# This team or user is the reviewer of last resort. -# ----------------------------------------------------------------------------- * @markurtz # ----------------------------------------------------------------------------- @@ -34,7 +27,7 @@ # ----------------------------------------------------------------------------- docs/ @markurtz *.md @markurtz -zensical.toml @markurtz +mkdocs.yml @markurtz # ----------------------------------------------------------------------------- # Security-Sensitive Files @@ -43,6 +36,7 @@ zensical.toml @markurtz # ----------------------------------------------------------------------------- SECURITY.md @markurtz .github/dependabot.yml @markurtz +.pre-commit-config.yaml @markurtz # ----------------------------------------------------------------------------- # Core Source Code diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 6f30346..a91e852 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,4 +1,17 @@ --- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. name: 🐛 Bug Report description: Create a report to help us improve rustarium labels: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index a212a41..df758ca 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,4 +1,17 @@ --- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. name: Feature Request description: Suggest an idea for rustarium labels: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cff0055..70272c1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,21 @@ + + diff --git a/.github/actions/oci/build/action.yml b/.github/actions/oci/build/action.yml index 68e32dd..2cddbbc 100644 --- a/.github/actions/oci/build/action.yml +++ b/.github/actions/oci/build/action.yml @@ -13,42 +13,76 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "OCI Builder" -description: "Builds container image and optionally saves it as a tarball" +description: "Builds container image and saves it as a tarball" inputs: - save-image: - description: "Whether to save the built image to a tarball archive (for testing in later\ - \ jobs)" - required: false - default: "false" python-version: - description: "Python version to set up" - required: false - default: "3.10" + description: "Python version to set up for tooling" + required: true force-run: description: "Always run even if no changes are detected" required: false default: "false" + build-type: + description: "Build type: dev, nightly, or release" + required: false + default: "dev" +outputs: + image-path: + description: "The path to the saved OCI image tarball" + value: "rustarium-latest.tar" runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + OCI_CHANGES: ${{ steps.filter.outputs.oci_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$OCI_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.oci_build == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} + - name: Resolve and set version + id: version-resolver + if: steps.check.outputs.run == 'true' + shell: bash + env: + BUILD_TYPE: ${{ inputs.build-type }} + run: |- + hatch run gitversioned write --version-type "$BUILD_TYPE" + VERSION=$(hatch project metadata | python3 -c "import json, sys; print(json.load(sys.stdin)['version'])") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Build OCI Image - if: steps.filter.outputs.oci_build == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' shell: bash + env: + BUILD_VERSION: ${{ steps.version-resolver.outputs.version }} + GIT_SHA: ${{ github.sha }} run: | - hatch run oci:build + hatch run oci:build --build-arg VERSION="$BUILD_VERSION" --build-arg GIT_SHA="$GIT_SHA" --build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" - name: Save OCI Image to Tarball - if: (steps.filter.outputs.oci_build == 'true' || inputs.force-run == 'true') && inputs.save-image - == 'true' + if: steps.check.outputs.run == 'true' shell: bash run: |- echo "[INFO] Serializing OCI image to tarball..." diff --git a/.github/actions/oci/publish/action.yml b/.github/actions/oci/publish/action.yml index 67b8a16..46d091e 100644 --- a/.github/actions/oci/publish/action.yml +++ b/.github/actions/oci/publish/action.yml @@ -15,43 +15,46 @@ name: "OCI Publisher" description: "Tags and pushes container image to GHCR, optionally loading it from a tar archive" inputs: + image-tar: + description: "The path to the built image tarball to load" + required: true image-tag: - description: "The tag to use for pushing" - required: false - default: "latest" - github-token: - description: "GitHub token for authentication" + description: "The primary tag to use for pushing" required: true - load-image: - description: "Whether to load the image from a tarball archive first" + alias-tag: + description: "An optional alias tag to use for pushing" required: false - default: "false" + default: "" runs: using: "composite" steps: - - name: Load OCI Image from Tarball - if: inputs.load-image == 'true' - shell: bash - run: | - if [ -f "rustarium-latest.tar" ]; then - echo "[INFO] Loading OCI image from rustarium-latest.tar..." - docker load -i rustarium-latest.tar - else - echo "[WARNING] rustarium-latest.tar not found! Proceeding without loading." - fi - name: Log in to GHCR uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ inputs.github-token }} - - name: Tag and Push Image + password: ${{ github.token }} + - name: Load, Tag, and Push Image shell: bash env: GITHUB_REPOSITORY: ${{ github.repository }} + IMAGE_TAR: ${{ inputs.image-tar }} IMAGE_TAG: ${{ inputs.image-tag }} + ALIAS_TAG: ${{ inputs.alias-tag }} run: |- - docker tag rustarium:latest ghcr.io/"$GITHUB_REPOSITORY":"$IMAGE_TAG" - docker tag rustarium:latest ghcr.io/"$GITHUB_REPOSITORY":latest - docker push ghcr.io/"$GITHUB_REPOSITORY":"$IMAGE_TAG" - docker push ghcr.io/"$GITHUB_REPOSITORY":latest + # Registry namespaces must be strictly lowercase + REPO_LOWER=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') + if [[ "$IMAGE_TAG" == nightly-* ]] && docker manifest inspect ghcr.io/"$REPO_LOWER":"$IMAGE_TAG" >/dev/null 2>&1; then + echo "[INFO] Nightly image tag $IMAGE_TAG already exists in GHCR. Skipping push." + else + echo "[INFO] Loading OCI image from $IMAGE_TAR..." + docker load -i "$IMAGE_TAR" + echo "[INFO] Tagging and pushing primary tag $IMAGE_TAG..." + docker tag rustarium:latest ghcr.io/"$REPO_LOWER":"$IMAGE_TAG" + docker push ghcr.io/"$REPO_LOWER":"$IMAGE_TAG" + if [ -n "$ALIAS_TAG" ]; then + echo "[INFO] Tagging and pushing alias tag $ALIAS_TAG..." + docker tag rustarium:latest ghcr.io/"$REPO_LOWER":"$ALIAS_TAG" + docker push ghcr.io/"$REPO_LOWER":"$ALIAS_TAG" + fi + fi diff --git a/.github/actions/oci/quality/action.yml b/.github/actions/oci/quality/action.yml index 216e72c..a7497a4 100644 --- a/.github/actions/oci/quality/action.yml +++ b/.github/actions/oci/quality/action.yml @@ -13,12 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "OCI Quality" -description: "Runs Dockerfile and Docker Compose linters" +description: "Runs Dockerfile and Docker Compose linters and security checks" inputs: python-version: - description: "Python version to set up" - required: false - default: "3.10" + description: "Python version to set up for tooling" + required: true force-run: description: "Always run even if no changes are detected" required: false @@ -27,26 +26,55 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + OCI_CHANGES: ${{ steps.filter.outputs.oci_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$OCI_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Install Hadolint - if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' - uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 - with: - tool: hadolint - - name: Install DCLint - if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' - shell: bash - run: npm install -g dclint@3.1.0 - - name: Run OCI Quality Checks - if: steps.filter.outputs.oci_quality == 'true' || inputs.force-run == 'true' + - name: Set up OCI Tools + if: steps.check.outputs.run == 'true' + uses: ./.github/actions/utility/setup-oci + - name: Run OCI Lint + if: steps.check.outputs.run == 'true' shell: bash run: |- hatch run oci:lint + - name: Run OCI Format + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run oci:format + - name: Build OCI Image + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run oci:build + - name: Run OCI Security Scan + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run oci:security diff --git a/.github/actions/oci/security/action.yml b/.github/actions/oci/security/action.yml deleted file mode 100644 index a07418a..0000000 --- a/.github/actions/oci/security/action.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: "OCI Security" -description: "Runs dockle and trivy security scans on the container image" -inputs: - python-version: - description: "Python version to set up" - required: false - default: "3.10" - force-run: - description: "Always run even if no changes are detected" - required: false - default: "false" -runs: - using: "composite" - steps: - - name: Detect changes - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 - id: filter - with: - filters: .github/paths-filter.yml - - name: Set up Python & Hatch - if: steps.filter.outputs.oci_security == 'true' || inputs.force-run == 'true' - uses: ./.github/actions/utility/setup-python - with: - python-version: ${{ inputs.python-version }} - - name: Install Dockle and Trivy - if: steps.filter.outputs.oci_security == 'true' || inputs.force-run == 'true' - uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 - with: - tool: dockle,trivy - - name: Run OCI Security Scan - if: steps.filter.outputs.oci_security == 'true' || inputs.force-run == 'true' - shell: bash - run: |- - hatch run oci:security diff --git a/.github/actions/oci/tests/action.yml b/.github/actions/oci/tests/action.yml index 4c13a53..13cace4 100644 --- a/.github/actions/oci/tests/action.yml +++ b/.github/actions/oci/tests/action.yml @@ -13,20 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "OCI Tests" -description: "Runs container structure tests against the built image" +description: "Runs OCI container structure tests (E2E) and no-op functional tests" inputs: - test-level: - description: "Test level: unit, integration, e2e, or all" - required: false - default: "all" - test-category: - description: "Test category (not applicable for container structure tests)" - required: false - default: "all" python-version: - description: "Python version to set up" - required: false - default: "3.10" + description: "Python version to set up for tooling" + required: true + image-tar: + description: "The path to a pre-built image tarball to load" + required: true force-run: description: "Always run even if no changes are detected" required: false @@ -35,44 +29,48 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + OCI_CHANGES: ${{ steps.filter.outputs.oci_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$OCI_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.oci_tests == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Install container-structure-test - if: steps.filter.outputs.oci_tests == 'true' || inputs.force-run == 'true' - shell: bash - run: |- - OS="$(uname -s | tr '[:upper:]' '[:lower:]')" - ARCH="$(uname -m)" - if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi - if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then ARCH="arm64"; fi - curl -LO "https://github.com/GoogleContainerTools/container-structure-test/releases/download/v1.22.1/container-structure-test-${OS}-${ARCH}" - chmod +x "container-structure-test-${OS}-${ARCH}" - sudo mv "container-structure-test-${OS}-${ARCH}" /usr/local/bin/container-structure-test - - name: Run OCI Structure Tests - if: steps.filter.outputs.oci_tests == 'true' || inputs.force-run == 'true' + - name: Set up OCI Tools + if: steps.check.outputs.run == 'true' + uses: ./.github/actions/utility/setup-oci + - name: Run OCI E2E Tests + if: steps.check.outputs.run == 'true' shell: bash + env: + IMAGE_TAR: ${{ inputs.image-tar }} run: |- - LEVEL="${{ inputs.test-level }}" - if [ "$LEVEL" = "e2e" ] || [ "$LEVEL" = "all" ]; then - # Optimize: check if image is already built/loaded by build job - if docker image inspect rustarium:latest >/dev/null 2>&1; then - echo "[INFO] OCI image rustarium:latest is already loaded. Running structure tests directly..." - hatch run -e oci python scripts/run_oci.py cstest - else - echo "[INFO] OCI image rustarium:latest not found. Building and running E2E tests..." - hatch run oci:tests-e2e - fi - else - if [ "$LEVEL" = "unit" ]; then - hatch run oci:tests-unit - elif [ "$LEVEL" = "integration" ]; then - hatch run oci:tests-int - fi + if [ ! -f "$IMAGE_TAR" ]; then + echo "[ERROR] Pre-built image tarball not found at: $IMAGE_TAR" + exit 1 fi + echo "[INFO] Loading OCI image from $IMAGE_TAR..." + docker load -i "$IMAGE_TAR" + hatch run oci:tests-e2e diff --git a/.github/actions/project/docs/action.yml b/.github/actions/project/docs/action.yml index 312cf13..de7738b 100644 --- a/.github/actions/project/docs/action.yml +++ b/.github/actions/project/docs/action.yml @@ -13,141 +13,156 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Project Docs Builder" -description: "Builds project documentation using Zensical and optionally deploys to GitHub\ - \ Pages" +description: "Builds project documentation using Zensical and mike versioning" inputs: - build-type: - description: "Deploy target type: dev-branch, dev, nightly, release, or post" + version-name: + description: "The version or folder name of the docs to build" required: true - alias: - description: "The alias to use for the documentation version (e.g., latest, nightly)" - required: true - include-coverage: - description: "Whether to download and include unit test coverage in the docs" - required: false - default: "false" - deploy: - description: "Whether to commit and deploy the built docs to the gh-pages branch" - required: false - default: "false" python-version: - description: "Python version to set up" - required: false - default: "3.10" + description: "Python version to set up for tooling" + required: true force-run: description: "Always run even if no changes are detected" required: false default: "false" outputs: - deployed-url: - description: "Path where the docs were deployed" - value: ${{ steps.dest.outputs.path }} + docs-url: + description: "The URL where the deployed docs will be accessible" + value: ${{ steps.url-step.outputs.docs-url }} runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + DOCS_CHANGES: ${{ steps.filter.outputs.docs_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$DOCS_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi + - name: Evaluate docs arguments + id: docs-args + if: steps.check.outputs.run == 'true' + shell: bash + env: + VERSION_NAME: ${{ inputs.version-name }} + run: | + BUILD_TYPE="" + ALIAS="" + if [[ "$VERSION_NAME" =~ ^pr-[0-9]+$ ]]; then + BUILD_TYPE="pr" + elif [[ "$VERSION_NAME" =~ ^main- ]]; then + BUILD_TYPE="main" + ALIAS="dev" + elif [[ "$VERSION_NAME" =~ ^nightly- ]]; then + BUILD_TYPE="nightly" + ALIAS="nightly" + elif [[ "$VERSION_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + BUILD_TYPE="release" + git fetch --tags --force 2>/dev/null || true + LATEST_TAG=$(git tag -l 'v[0-9]*' | sort -V | tail -n 1) + if [ "$VERSION_NAME" = "$LATEST_TAG" ]; then + ALIAS="latest" + else + ALIAS="" + fi + else + BUILD_TYPE="release" + ALIAS="" + fi + echo "build-type=$BUILD_TYPE" >> "$GITHUB_OUTPUT" + echo "alias=$ALIAS" >> "$GITHUB_OUTPUT" + echo "[INFO] Evaluated build-type: $BUILD_TYPE, alias: $ALIAS for version: $VERSION_NAME" - name: Set up Python & Hatch - if: steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Configure Git Credentials - if: (steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true') && inputs.deploy - == 'true' + - name: Build documentation source + if: steps.check.outputs.run == 'true' shell: bash - run: | - git config user.name github-actions[bot] - git config user.email github-actions[bot]@users.noreply.github.com - - name: Download Python test coverage - if: (steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true') && inputs.include-coverage - == 'true' - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 - with: - pattern: coverage-report-unit-python-py${{ inputs.python-version }}-* - path: coverage/python - merge-multiple: true - continue-on-error: true - - name: Download Rust test coverage - if: (steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true') && inputs.include-coverage - == 'true' - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + run: |- + echo "[INFO] Building documentation source..." + hatch run all:docs + - name: Deploy PR documentation preview + if: steps.check.outputs.run == 'true' && steps.docs-args.outputs.build-type == 'pr' + uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v4.0.0 with: - pattern: coverage-report-unit-rust-* - path: coverage/rust - merge-multiple: true - continue-on-error: true - - name: Compute deployment destination path - if: steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true' - id: dest + github_token: ${{ github.token }} + publish_branch: gh-pages + publish_dir: ./site + destination_dir: review/${{ inputs.version-name }} + keep_files: true + user_name: "github-actions[bot]" + user_email: "github-actions[bot]@users.noreply.github.com" + - name: Set up Git credentials + if: steps.check.outputs.run == 'true' && steps.docs-args.outputs.build-type != 'pr' + uses: ./.github/actions/utility/setup-git + - name: Deploy versioned documentation using mike + if: steps.check.outputs.run == 'true' && steps.docs-args.outputs.build-type != 'pr' shell: bash env: - BUILD_TYPE: ${{ inputs.build-type }} - PR_NUMBER: ${{ github.event.pull_request.number }} - REF_NAME: ${{ github.ref_name }} - ALIAS: ${{ inputs.alias }} - run: | - case "$BUILD_TYPE" in - dev-branch) echo "path=pr-$PR_NUMBER" >> "$GITHUB_OUTPUT" ;; - dev) echo "path=dev" >> "$GITHUB_OUTPUT" ;; - nightly) echo "path=nightly" >> "$GITHUB_OUTPUT" ;; - release) echo "path=$REF_NAME" >> "$GITHUB_OUTPUT" ;; - post) echo "path=post" >> "$GITHUB_OUTPUT" ;; - *) echo "path=$ALIAS" >> "$GITHUB_OUTPUT" ;; - esac - - name: Build and deploy using Git - if: steps.filter.outputs.project_docs == 'true' || inputs.force-run == 'true' + GITHUB_TOKEN: ${{ github.token }} + VERSION_NAME: ${{ inputs.version-name }} + ALIAS: ${{ steps.docs-args.outputs.alias }} + BUILD_TYPE: ${{ steps.docs-args.outputs.build-type }} + run: |- + echo "[INFO] Deploying version $VERSION_NAME using mike..." + if [ -n "$ALIAS" ]; then + hatch run mike deploy -F zensical.toml --push --update-aliases "$VERSION_NAME" "$ALIAS" + else + hatch run mike deploy -F zensical.toml --push "$VERSION_NAME" + fi + if [ "$BUILD_TYPE" = "release" ] && [ "$ALIAS" = "latest" ]; then + echo "[INFO] Setting default version to latest..." + hatch run mike set-default -F zensical.toml --push latest + fi + if [ "$BUILD_TYPE" = "main" ] || [ "$BUILD_TYPE" = "nightly" ]; then + echo "[INFO] Cleaning up older versions of type: $BUILD_TYPE..." + TO_DELETE=$(hatch run mike list -F zensical.toml | awk '{print $1}' | grep "^${BUILD_TYPE}-" | grep -v "^${VERSION_NAME}$" || true) + if [ -n "$TO_DELETE" ]; then + echo "[INFO] Deleting old versions: $TO_DELETE" + hatch run mike delete -F zensical.toml --push $TO_DELETE + else + echo "[INFO] No old versions of type $BUILD_TYPE found to delete." + fi + fi + - name: Compute documentation URL + id: url-step + if: steps.check.outputs.run == 'true' shell: bash env: - VERSION_PATH: ${{ steps.dest.outputs.path }} - ALIAS: ${{ inputs.alias }} - BUILD_TYPE: ${{ inputs.build-type }} - DEPLOY: ${{ inputs.deploy }} + REPO_OWNER: ${{ github.repository_owner }} + REPO: ${{ github.repository }} + BUILD_TYPE: ${{ steps.docs-args.outputs.build-type }} + VERSION_NAME: ${{ inputs.version-name }} + ALIAS: ${{ steps.docs-args.outputs.alias }} run: |- - # Build documentation - hatch run project:docs - if [ "$DEPLOY" = "true" ]; then - # Save built site files outside repo - mv site ../site-temp - - # Checkout gh-pages branch - git fetch origin gh-pages:gh-pages || true - git checkout gh-pages || git checkout --orphan gh-pages - - # Clean target directory and copy new files - rm -rf "$VERSION_PATH" - mkdir -p "$VERSION_PATH" - cp -r ../site-temp/* "$VERSION_PATH"/ - - # Deploy to alias path if alias is configured - if [ "$ALIAS" != "" ] && [ "$ALIAS" != "$VERSION_PATH" ]; then - rm -rf "$ALIAS" - mkdir -p "$ALIAS" - cp -r ../site-temp/* "$ALIAS"/ - fi - - # Set default redirect if release build - if [ "$BUILD_TYPE" == "release" ]; then - printf '%s\n' \ - '' \ - '' \ - '' \ - " " \ - ' ' \ - '' \ - '' \ - "

Redirecting to latest documentation...

" \ - '' \ - '' > index.html - fi - - # Commit changes and push - git add -A - git commit -m "docs: deploy documentation for $VERSION_PATH" || echo "No changes to commit" - git push origin gh-pages + OWNER_LOWER=$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]') + REPO_NAME=$(echo "$REPO" | cut -d'/' -f2) + BASE_URL="https://${OWNER_LOWER}.github.io/${REPO_NAME}" + if [ "$BUILD_TYPE" = "pr" ]; then + DOCS_URL="${BASE_URL}/review/${VERSION_NAME}/" + elif [ -n "$ALIAS" ]; then + DOCS_URL="${BASE_URL}/${ALIAS}/" + else + DOCS_URL="${BASE_URL}/${VERSION_NAME}/" fi + echo "docs-url=$DOCS_URL" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/project/quality/action.yml b/.github/actions/project/quality/action.yml index 25cf6d1..22ae24e 100644 --- a/.github/actions/project/quality/action.yml +++ b/.github/actions/project/quality/action.yml @@ -13,12 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Project Quality" -description: "Runs project linting and formatting checks (mdformat, yamlfix, yamllint, taplo)" +description: "Runs project linting, formatting, and security checks" inputs: python-version: description: "Python version to set up" - required: false - default: "3.10" + required: true force-run: description: "Always run even if no changes are detected" required: false @@ -27,17 +26,47 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + PROJECT_CHANGES: ${{ steps.filter.outputs.project_changes }} + OCI_CHANGES: ${{ steps.filter.outputs.oci_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" || \ + "$PROJECT_CHANGES" == "true" || \ + "$OCI_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.project_quality == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Run Project Quality Checks - if: steps.filter.outputs.project_quality == 'true' || inputs.force-run == 'true' + - name: Run Project Lint + if: steps.check.outputs.run == 'true' shell: bash run: |- hatch run project:lint + - name: Run Project Format + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run project:format + - name: Run Project Security Checks + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run project:security diff --git a/.github/actions/project/security/action.yml b/.github/actions/project/security/action.yml deleted file mode 100644 index 8463252..0000000 --- a/.github/actions/project/security/action.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: "Project Security" -description: "Runs project security checks (detect-secrets, checkov, actionlint, dependency-review)" -inputs: - python-version: - description: "Python version to set up" - required: false - default: "3.10" - force-run: - description: "Always run even if no changes are detected" - required: false - default: "false" -runs: - using: "composite" - steps: - - name: Detect changes - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 - id: filter - with: - filters: .github/paths-filter.yml - - name: Set up Python & Hatch - if: steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true' - uses: ./.github/actions/utility/setup-python - with: - python-version: ${{ inputs.python-version }} - - name: Run Project Security Checks - if: steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true' - shell: bash - run: |- - hatch run project:security - - name: Lint GitHub Actions Workflows - if: steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true' - uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0 - - name: Dependency Review - if: (steps.filter.outputs.project_security == 'true' || inputs.force-run == 'true') - && github.event_name == 'pull_request' - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/actions/project/tests/action.yml b/.github/actions/project/tests/action.yml index 627a518..d746662 100644 --- a/.github/actions/project/tests/action.yml +++ b/.github/actions/project/tests/action.yml @@ -13,24 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Project Tests" -description: "Runs project tests (markdown link check, phmdoctest) or end-to-end (e2e) tests" +description: "Runs project integration (doc) tests, E2E (examples) tests, and link checks" inputs: - test-level: - description: "Test level: unit, integration, e2e, or all" - required: false - default: "all" - test-category: - description: "Test category: smoke, sanity, regression, or all (applies to doc tests)" - required: false - default: "all" - extra-args: - description: "Extra arguments to pass to the tests" - required: false - default: "" python-version: description: "Python version to set up" + required: true + test-type: + description: "Type of tests to run: functional, e2e, or link-checks" required: false - default: "3.10" + default: "functional" force-run: description: "Always run even if no changes are detected" required: false @@ -39,56 +30,48 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + PROJECT_CHANGES: ${{ steps.filter.outputs.project_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" || \ + "$PROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.project_tests == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Run Project Tests - if: steps.filter.outputs.project_tests == 'true' || inputs.force-run == 'true' + - name: Run Project Functional Tests + if: steps.check.outputs.run == 'true' && inputs.test-type == 'functional' shell: bash run: |- - LEVEL="${{ inputs.test-level }}" - CATEGORY="${{ inputs.test-category }}" - EXTRA="${{ inputs.extra-args }}" - ARGS="$EXTRA" - if [ "$CATEGORY" != "all" ] && [ -n "$CATEGORY" ]; then - ARGS="-m $CATEGORY $EXTRA" - fi - run_unit() { - echo "[INFO] No unit tests are defined for the project environment." - } - run_int() { - echo "[INFO] Running Project integration tests (doc tests)..." - # Bypasses direct pyproject.toml arguments collision - hatch run -e project python scripts/generate_doc_tests.py - hatch run -e python pytest .tests/docs $ARGS - } - run_e2e() { - echo "[INFO] Running Project E2E tests (link checking)..." - hatch run project:tests-e2e $EXTRA - } - case "$LEVEL" in - unit) - run_unit - ;; - integration) - run_int - ;; - e2e) - run_e2e - ;; - all) - run_unit - run_int - run_e2e - ;; - *) - echo "Unknown test level: $LEVEL" - exit 1 - ;; - esac + echo "[INFO] Running Project integration tests (doc tests)..." + hatch run project:tests-func + - name: Run Project E2E Tests + if: steps.check.outputs.run == 'true' && inputs.test-type == 'e2e' + shell: bash + run: |- + echo "[INFO] Running Project E2E tests (examples)..." + hatch run project:tests-e2e + - name: Run Project Link Checks + if: steps.check.outputs.run == 'true' && inputs.test-type == 'link-checks' + shell: bash + run: |- + echo "[INFO] Running Project Link Checks..." + hatch run project:link-checks diff --git a/.github/actions/python/build/action.yml b/.github/actions/python/build/action.yml index c028932..668ce2b 100644 --- a/.github/actions/python/build/action.yml +++ b/.github/actions/python/build/action.yml @@ -17,12 +17,15 @@ description: "Builds the python wheel and sdist package using Hatch" inputs: python-version: description: "Python version to build package against" - required: false - default: "3.10" + required: true force-run: description: "Always run even if no changes are detected" required: false default: "false" + build-type: + description: "Build type: dev, nightly, or release" + required: false + default: "dev" outputs: location: description: "Directory where artifacts are built" @@ -31,17 +34,65 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.python_build == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} + - name: Resolve and set version + id: version-resolver + if: steps.check.outputs.run == 'true' + shell: bash + env: + BUILD_TYPE: ${{ inputs.build-type }} + run: |- + hatch run gitversioned write --version-type "$BUILD_TYPE" + VERSION=$(hatch project metadata | python3 -c "import json, sys; print(json.load(sys.stdin)['version'])") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Build Package - if: steps.filter.outputs.python_build == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' shell: bash run: |- hatch build + - name: Validate Built Version + if: steps.check.outputs.run == 'true' + shell: bash + env: + BUILD_VERSION: ${{ steps.version-resolver.outputs.version }} + run: |- + built_version=$(hatch project metadata 2>/dev/null | python3 -c "import json, sys; print(json.load(sys.stdin)['version'])") + echo "[INFO] Expected version: $BUILD_VERSION" + echo "[INFO] Hatch metadata version: $built_version" + if [ "$built_version" != "$BUILD_VERSION" ]; then + echo "[ERROR] Hatch metadata version ($built_version) does not match expected build-version ($BUILD_VERSION)" + exit 1 + fi + WHL_COUNT=$(find dist -name "rustarium-${BUILD_VERSION}*.whl" | wc -l) + TAR_COUNT=$(find dist -name "rustarium-${BUILD_VERSION}*.tar.gz" | wc -l) + if [ "$WHL_COUNT" -eq 0 ] && [ "$TAR_COUNT" -eq 0 ]; then + echo "[ERROR] Expected build artifacts for version $BUILD_VERSION were not found in dist/" + exit 1 + fi + echo "[INFO] Build validation successful. Found matching artifacts." diff --git a/.github/actions/python/publish/action.yml b/.github/actions/python/publish/action.yml index 97a345b..8aae95a 100644 --- a/.github/actions/python/publish/action.yml +++ b/.github/actions/python/publish/action.yml @@ -16,46 +16,35 @@ name: "Python Publish" description: "Downloads python package build, attests build provenance, publishes to PyPI,\ \ and optionally creates GitHub Release" inputs: - artifact-name: - description: "The name of the build artifact to download" - required: false - default: "" create-release: description: "Whether to create a GitHub Release (requires tags)" required: false default: "false" - github-token: - description: "GitHub token for Release creation" - required: false - default: "" - pypi-token: - description: "Optional PyPI token (not needed if trusted publishing OIDC is configured)" - required: false - default: "" runs: using: "composite" steps: - - name: Download build artifact - if: inputs.artifact-name != '' - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 - with: - name: ${{ inputs.artifact-name }} - path: dist/ - name: Generate artifact attestations uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: subject-path: dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + uses: docker://ghcr.io/pypa/gh-action-pypi-publish:v1.14.0 with: packages-dir: dist/ - password: ${{ inputs.pypi-token }} + user: __token__ + password: "" + repository-url: https://upload.pypi.org/legacy/ + verify-metadata: true + skip-existing: false + verbose: true + print-hash: true + attestations: true - name: Create GitHub Release - if: inputs.create-release == 'true' && startsWith(github.ref, 'refs/tags/') + if: inputs.create-release == 'true' uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 with: files: dist/* generate_release_notes: true - prerelease: false + prerelease: auto env: - GITHUB_TOKEN: ${{ inputs.github-token }} + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/actions/python/quality/action.yml b/.github/actions/python/quality/action.yml index 3e99121..c6ba5eb 100644 --- a/.github/actions/python/quality/action.yml +++ b/.github/actions/python/quality/action.yml @@ -13,12 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Python Quality" -description: "Runs python lint and types check" +description: "Runs python lint, format, type checks, and security audit" inputs: python-version: description: "Python version to run checks against" - required: false - default: "3.10" + required: true force-run: description: "Always run even if no changes are detected" required: false @@ -27,18 +26,56 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + EXAMPLES_CHANGES: ${{ steps.filter.outputs.examples_changes }} + SCRIPTS_CHANGES: ${{ steps.filter.outputs.scripts_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + TESTS_CHANGES: ${{ steps.filter.outputs.tests_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$EXAMPLES_CHANGES" == "true" || \ + "$SCRIPTS_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$TESTS_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.python_quality == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Run Python Lint & Types - if: steps.filter.outputs.python_quality == 'true' || inputs.force-run == 'true' + - name: Run Python Lint + if: steps.check.outputs.run == 'true' shell: bash run: |- hatch run python:lint + - name: Run Python Format + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run python:format + - name: Run Python Type Checks + if: steps.check.outputs.run == 'true' + shell: bash + run: |- hatch run python:types + - name: Run Python Security + if: steps.check.outputs.run == 'true' + shell: bash + run: |- + hatch run python:security diff --git a/.github/actions/python/security/action.yml b/.github/actions/python/security/action.yml deleted file mode 100644 index be419ff..0000000 --- a/.github/actions/python/security/action.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: "Python Security" -description: "Runs python security scan (semgrep, pip-audit, ruff S)" -inputs: - python-version: - description: "Python version to run checks against" - required: false - default: "3.10" - force-run: - description: "Always run even if no changes are detected" - required: false - default: "false" -runs: - using: "composite" - steps: - - name: Detect changes - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 - id: filter - with: - filters: .github/paths-filter.yml - - name: Set up Python & Hatch - if: steps.filter.outputs.python_security == 'true' || inputs.force-run == 'true' - uses: ./.github/actions/utility/setup-python - with: - python-version: ${{ inputs.python-version }} - - name: Run Python Security Audit - if: steps.filter.outputs.python_security == 'true' || inputs.force-run == 'true' - shell: bash - run: |- - hatch run python:security diff --git a/.github/actions/python/tests/action.yml b/.github/actions/python/tests/action.yml index c71bfaf..2e569ef 100644 --- a/.github/actions/python/tests/action.yml +++ b/.github/actions/python/tests/action.yml @@ -13,116 +13,91 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Python Tests" -description: "Runs python unit, integration, and e2e tests with optional coverage reporting" +description: "Runs python unit, integration, and e2e tests as separate steps with output coverage\ + \ reporting" inputs: python-version: description: "Python version to run checks against" + required: true + test-type: + description: "Type of tests to run: functional or e2e" required: false - default: "3.10" - test-level: - description: "Test level: unit, integration, e2e, or all" - required: false - default: "all" + default: "functional" test-category: - description: "Test category: smoke, sanity, regression, or all" + description: "Test category: smoke, sanity, regression, or all (supports combinations\ + \ like 'smoke or sanity')" required: false default: "all" - generate-coverage: - description: "Generate test coverage report" - required: false - default: "false" - extra-args: - description: "Extra arguments to pass to pytest" - required: false - default: "" force-run: description: "Always run even if no changes are detected" required: false default: "false" +outputs: + coverage-reports: + description: "Paths to the generated coverage report(s)" + value: ${{ steps.func-tests.outputs.coverage-reports || steps.e2e-tests.outputs.coverage-reports || '' }} runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + SRC_CHANGES: ${{ steps.filter.outputs.src_changes }} + TESTS_CHANGES: ${{ steps.filter.outputs.tests_changes }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$SRC_CHANGES" == "true" || \ + "$TESTS_CHANGES" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi + - name: Evaluate test arguments + id: args + if: steps.check.outputs.run == 'true' + shell: bash + env: + TEST_CATEGORY: ${{ inputs.test-category }} + run: |- + if [ "$TEST_CATEGORY" != "all" ] && [ -n "$TEST_CATEGORY" ]; then + echo "args=-m \"$TEST_CATEGORY\"" >> "$GITHUB_OUTPUT" + else + echo "args=" >> "$GITHUB_OUTPUT" + fi - name: Set up Python & Hatch - if: steps.filter.outputs.python_tests == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-python with: python-version: ${{ inputs.python-version }} - - name: Run Python Tests - if: steps.filter.outputs.python_tests == 'true' || inputs.force-run == 'true' + - name: Run Python Functional Tests + id: func-tests + if: steps.check.outputs.run == 'true' && inputs.test-type == 'functional' shell: bash run: |- - LEVEL="${{ inputs.test-level }}" - CATEGORY="${{ inputs.test-category }}" - COV="${{ inputs.generate-coverage }}" - EXTRA="${{ inputs.extra-args }}" - - # Construct pytest marker arguments if category is specified - ARGS="$EXTRA" - if [ "$CATEGORY" != "all" ] && [ -n "$CATEGORY" ]; then - ARGS="-m $CATEGORY $EXTRA" - fi - run_unit() { - echo "[INFO] Running Python unit tests..." - if [ "$COV" = "true" ]; then - hatch run python:tests-unit-cov $ARGS - else - hatch run python:tests-unit $ARGS - fi - } - run_int() { - echo "[INFO] Running Python integration tests..." - if [ "$COV" = "true" ]; then - hatch run python:tests-int-cov $ARGS - else - hatch run python:tests-int $ARGS - fi - } - run_e2e() { - # Check if a pre-built wheel is present in dist/ - if [ -d "dist" ] && [ "$(find dist -name '*.whl' | wc -l)" -gt 0 ]; then - echo "[INFO] Found pre-built wheel in dist/. Installing and running E2E tests..." - hatch run -e python pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium - if [ "$COV" = "true" ]; then - hatch run -e python pytest --cov=rustarium --cov-report=term --cov-report=markdown:coverage/python/coverage_tests-e2e.md tests/e2e $ARGS - else - hatch run -e python pytest tests/e2e $ARGS - fi - else - echo "[INFO] No pre-built wheel found in dist/. Building and running E2E tests locally..." - if [ "$COV" = "true" ]; then - hatch run python:tests-e2e-cov $ARGS - else - hatch run python:tests-e2e $ARGS - fi - fi - } - case "$LEVEL" in - unit) - run_unit - ;; - integration) - run_int - ;; - e2e) - run_e2e - ;; - all) - # Run functional (unit + integration) - if [ "$COV" = "true" ]; then - hatch run python:tests-func-cov $ARGS - else - hatch run python:tests-func $ARGS - fi - # Run E2E - run_e2e - ;; - *) - echo "Unknown test level: $LEVEL" - exit 1 - ;; - esac + hatch run python:tests-func-cov ${{ steps.args.outputs.args }} + { + echo "coverage-reports<> "$GITHUB_OUTPUT" + - name: Run Python E2E Tests + id: e2e-tests + if: steps.check.outputs.run == 'true' && inputs.test-type == 'e2e' + shell: bash + run: |- + hatch run python:tests-e2e-cov ${{ steps.args.outputs.args }} + echo "coverage-reports=coverage/python/coverage_tests-e2e.md" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/rust/build/action.yml b/.github/actions/rust/build/action.yml deleted file mode 100644 index 8acf8a3..0000000 --- a/.github/actions/rust/build/action.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: "Rust Build" -description: "Builds the cargo workspace to verify compilation" -inputs: - python-version: - description: "Python version to set up" - required: false - default: "3.10" - force-run: - description: "Always run even if no changes are detected" - required: false - default: "false" -runs: - using: "composite" - steps: - - name: Detect changes - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 - id: filter - with: - filters: .github/paths-filter.yml - - name: Set up Rust Environment - if: steps.filter.outputs.rust_build == 'true' || inputs.force-run == 'true' - uses: ./.github/actions/utility/setup-rust - with: - python-version: ${{ inputs.python-version }} - - name: Build Rust Workspace - if: steps.filter.outputs.rust_build == 'true' || inputs.force-run == 'true' - shell: bash - run: |- - cargo build --workspace --all-targets diff --git a/.github/actions/rust/quality/action.yml b/.github/actions/rust/quality/action.yml index 68328d5..0f8f60a 100644 --- a/.github/actions/rust/quality/action.yml +++ b/.github/actions/rust/quality/action.yml @@ -13,12 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Rust Quality" -description: "Runs cargo fmt and clippy" +description: "Runs rust lint and type checks" inputs: python-version: - description: "Python version to set up" - required: false - default: "3.10" + description: "Python version to run checks against" + required: true force-run: description: "Always run even if no changes are detected" required: false @@ -27,19 +26,41 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + RUST_QUALITY: ${{ steps.filter.outputs.rust_quality }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$RUST_QUALITY" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Rust Environment - if: steps.filter.outputs.rust_quality == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-rust with: components: rustfmt, clippy python-version: ${{ inputs.python-version }} - - name: Run Rust Lint & Types - if: steps.filter.outputs.rust_quality == 'true' || inputs.force-run == 'true' + - name: Run Rust Lint + if: steps.check.outputs.run == 'true' shell: bash run: |- hatch run rust:lint + - name: Run Rust Type Checks + if: steps.check.outputs.run == 'true' + shell: bash + run: |- hatch run rust:types diff --git a/.github/actions/rust/security/action.yml b/.github/actions/rust/security/action.yml index 13fd36c..18fab3a 100644 --- a/.github/actions/rust/security/action.yml +++ b/.github/actions/rust/security/action.yml @@ -16,9 +16,8 @@ name: "Rust Security" description: "Runs cargo audit and cargo deny check" inputs: python-version: - description: "Python version to set up" - required: false - default: "3.10" + description: "Python version to run checks against" + required: true force-run: description: "Always run even if no changes are detected" required: false @@ -27,22 +26,40 @@ runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + RUST_SECURITY: ${{ steps.filter.outputs.rust_security }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$RUST_SECURITY" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi - name: Set up Rust Environment - if: steps.filter.outputs.rust_security == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-rust with: python-version: ${{ inputs.python-version }} - name: Install cargo-deny and cargo-audit - if: steps.filter.outputs.rust_security == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 with: tool: cargo-deny,cargo-audit - name: Run Rust Security Audit - if: steps.filter.outputs.rust_security == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' shell: bash run: |- hatch run rust:security diff --git a/.github/actions/rust/tests/action.yml b/.github/actions/rust/tests/action.yml index ba9eb6f..834a4d4 100644 --- a/.github/actions/rust/tests/action.yml +++ b/.github/actions/rust/tests/action.yml @@ -13,109 +13,91 @@ # See the License for the specific language governing permissions and # limitations under the License. name: "Rust Tests" -description: "Runs cargo tests with optional llvm-cov coverage reporting" +description: "Runs rust unit, integration, and e2e tests as separate steps with output coverage\ + \ reporting" inputs: - test-level: - description: "Test level: unit, integration, e2e, or all" + python-version: + description: "Python version to run checks against" + required: true + test-type: + description: "Type of tests to run: functional or e2e" required: false - default: "all" + default: "functional" test-category: description: "Test category: smoke, sanity, regression, or all (matches test name substring)" required: false default: "all" - generate-coverage: - description: "Generate test coverage report" - required: false - default: "false" - extra-args: - description: "Extra arguments to pass to Cargo" - required: false - default: "" - python-version: - description: "Python version to set up" - required: false - default: "3.10" force-run: description: "Always run even if no changes are detected" required: false default: "false" +outputs: + coverage-reports: + description: "Paths to the generated coverage report(s)" + value: ${{ steps.func-tests.outputs.coverage-reports || steps.e2e-tests.outputs.coverage-reports || '' }} runs: using: "composite" steps: - name: Detect changes + if: inputs.force-run != 'true' uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 id: filter with: filters: .github/paths-filter.yml + - name: Evaluate changes + id: check + shell: bash + env: + FORCE_RUN: ${{ inputs.force-run }} + GITHUB_CHANGES: ${{ steps.filter.outputs.github_changes }} + RUST_TESTS: ${{ steps.filter.outputs.rust_tests }} + PYPROJECT_CHANGES: ${{ steps.filter.outputs.pyproject_changes }} + run: | + if [[ "$FORCE_RUN" == "true" || \ + "$GITHUB_CHANGES" == "true" || \ + "$RUST_TESTS" == "true" || \ + "$PYPROJECT_CHANGES" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi + - name: Evaluate test arguments + id: args + if: steps.check.outputs.run == 'true' + shell: bash + env: + TEST_CATEGORY: ${{ inputs.test-category }} + run: |- + if [ "$TEST_CATEGORY" != "all" ] && [ -n "$TEST_CATEGORY" ]; then + echo "args=-- $TEST_CATEGORY" >> "$GITHUB_OUTPUT" + else + echo "args=" >> "$GITHUB_OUTPUT" + fi - name: Set up Rust Environment - if: steps.filter.outputs.rust_tests == 'true' || inputs.force-run == 'true' + if: steps.check.outputs.run == 'true' uses: ./.github/actions/utility/setup-rust with: python-version: ${{ inputs.python-version }} - name: Install cargo-llvm-cov - if: (steps.filter.outputs.rust_tests == 'true' || inputs.force-run == 'true') && inputs.generate-coverage - == 'true' + if: steps.check.outputs.run == 'true' uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 with: tool: cargo-llvm-cov - - name: Run Rust Tests - if: steps.filter.outputs.rust_tests == 'true' || inputs.force-run == 'true' + - name: Run Rust Functional Tests + id: func-tests + if: steps.check.outputs.run == 'true' && inputs.test-type == 'functional' shell: bash run: |- - LEVEL="${{ inputs.test-level }}" - CATEGORY="${{ inputs.test-category }}" - COV="${{ inputs.generate-coverage }}" - EXTRA="${{ inputs.extra-args }}" - - # If a category is specified, use it as a test name filter for cargo test - ARGS="$EXTRA" - if [ "$CATEGORY" != "all" ] && [ -n "$CATEGORY" ]; then - ARGS="-- $CATEGORY $EXTRA" - fi - run_unit() { - echo "[INFO] Running Rust unit tests..." - if [ "$COV" = "true" ]; then - hatch run rust:tests-unit-cov $ARGS - else - hatch run rust:tests-unit $ARGS - fi - } - run_int() { - echo "[INFO] Running Rust integration tests..." - if [ "$COV" = "true" ]; then - hatch run rust:tests-int-cov $ARGS - else - hatch run rust:tests-int $ARGS - fi - } - run_e2e() { - echo "[INFO] Running Rust E2E tests..." - if [ "$COV" = "true" ]; then - hatch run rust:tests-e2e-cov $ARGS - else - hatch run rust:tests-e2e $ARGS - fi - } - case "$LEVEL" in - unit) - run_unit - ;; - integration) - run_int - ;; - e2e) - run_e2e - ;; - all) - echo "[INFO] Running all Rust tests..." - if [ "$COV" = "true" ]; then - hatch run rust:tests-func-cov $ARGS - else - hatch run rust:tests-func $ARGS - fi - ;; - *) - echo "Unknown test level: $LEVEL" - exit 1 - ;; - esac + hatch run rust:tests-func-cov ${{ steps.args.outputs.args }} + { + echo "coverage-reports<> "$GITHUB_OUTPUT" + - name: Run Rust E2E Tests + id: e2e-tests + if: steps.check.outputs.run == 'true' && inputs.test-type == 'e2e' + shell: bash + run: |- + hatch run rust:tests-e2e-cov ${{ steps.args.outputs.args }} + echo "coverage-reports=coverage/rust/coverage_tests-e2e.md" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/utility/comment-upsert/action.yml b/.github/actions/utility/comment-upsert/action.yml new file mode 100644 index 0000000..e15d6b3 --- /dev/null +++ b/.github/actions/utility/comment-upsert/action.yml @@ -0,0 +1,74 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Upsert PR Comment" +description: "Posts a new comment on a pull request, or updates an existing one if a specific\ + \ marker/signature is found." +inputs: + pr-number: + description: "The pull request number" + required: true + marker: + description: "A hidden HTML comment marker to uniquely identify the comment" + required: true + body: + description: "The markdown body of the comment" + required: true +runs: + using: "composite" + steps: + - name: Upsert Comment + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_NUMBER: ${{ inputs.pr-number }} + MARKER: ${{ inputs.marker }} + BODY: ${{ inputs.body }} + with: + github-token: ${{ github.token }} + script: |- + const prNumber = parseInt(process.env.PR_NUMBER, 10); + const marker = process.env.MARKER; + const body = process.env.BODY; + if (!marker) { + core.setFailed("marker input is required"); + return; + } + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100 + }); + let commentBody = body; + if (!commentBody.includes(marker)) { + commentBody = `${commentBody}\n\n${marker}`; + } + const existingComment = comments.data.find(c => c.user.login === 'github-actions[bot]' && c.body.includes(marker)); + if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: commentBody + }); + console.log(`Updated comment for marker: ${marker}`); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody + }); + console.log(`Created new comment for marker: ${marker}`); + } diff --git a/.github/actions/utility/setup-git/action.yml b/.github/actions/utility/setup-git/action.yml new file mode 100644 index 0000000..116aa39 --- /dev/null +++ b/.github/actions/utility/setup-git/action.yml @@ -0,0 +1,39 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Set Up Git" +description: "Standardized setup and configuration for Git credentials and environment in\ + \ CI" +inputs: + user-name: + description: "Git user name" + required: false + default: "github-actions[bot]" + user-email: + description: "Git user email" + required: false + default: "github-actions[bot]@users.noreply.github.com" +runs: + using: "composite" + steps: + - name: Configure Git Credentials and Environment + shell: bash + env: + USER_NAME: ${{ inputs.user-name }} + USER_EMAIL: ${{ inputs.user-email }} + run: |- + echo "[INFO] Configuring standardized Git user and environment..." + git config --global user.name "$USER_NAME" + git config --global user.email "$USER_EMAIL" + git config --global --add safe.directory "$GITHUB_WORKSPACE" diff --git a/.github/actions/utility/setup-oci/action.yml b/.github/actions/utility/setup-oci/action.yml new file mode 100644 index 0000000..6efe873 --- /dev/null +++ b/.github/actions/utility/setup-oci/action.yml @@ -0,0 +1,58 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Setup OCI Environment" +description: "Utility action to install OCI quality and security tools (hadolint, dclint,\ + \ dockle, trivy)" +runs: + using: "composite" + steps: + - name: Install Trivy + uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7 + with: + tool: trivy + - name: Install Hadolint + shell: bash + run: |- + mkdir -p "$HOME/.local/bin" + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + ARCH=$(uname -m | sed -e 's/aarch64/arm64/') + if [ "$OS" = "darwin" ]; then + OS="macos" + elif [ "$OS" = "linux" ] && [ "$ARCH" = "x86_64" ]; then + OS="Linux" + fi + HADOLINT_VERSION="v2.12.0" + curl -sSL -o "$HOME/.local/bin/hadolint" "https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-${OS}-${ARCH}" + chmod +x "$HOME/.local/bin/hadolint" + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Install Dockle + shell: bash + run: |- + VERSION="v0.4.15" + curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/${VERSION}/dockle_${VERSION#v}_Linux-64bit.deb" + sudo dpkg -i dockle.deb + rm dockle.deb + - name: Install DCLint + shell: bash + run: npm install -g dclint@3.1.0 + - name: Install container-structure-test + shell: bash + run: |- + mkdir -p "$HOME/.local/bin" + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/') + curl -sSL -o "$HOME/.local/bin/container-structure-test" "https://github.com/GoogleContainerTools/container-structure-test/releases/download/v1.22.1/container-structure-test-${OS}-${ARCH}" + chmod +x "$HOME/.local/bin/container-structure-test" + echo "$HOME/.local/bin" >> $GITHUB_PATH diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 73c9c82..cabc477 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,17 +1,17 @@ --- -# ============================================================================= -# dependabot.yml — Automated Dependency Management +# Copyright 2026 markurtz # -# GitHub Dependabot natively creates PRs for outdated dependencies. -# All updates are grouped to minimize PR noise (one PR per ecosystem/week). +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Supported ecosystems: -# • github-actions — workflow action version pins -# • pip — Python packages -# • docker — Base image tags +# http://www.apache.org/licenses/LICENSE-2.0 # -# After PRs are opened, weekly.yml surfaces them via Slack for human review. -# ============================================================================= +# 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. version: 2 updates: # ── GitHub Actions ────────────────────────────────────────────────────────── diff --git a/.github/paths-filter.yml b/.github/paths-filter.yml index a65d519..57a447d 100644 --- a/.github/paths-filter.yml +++ b/.github/paths-filter.yml @@ -12,61 +12,40 @@ # 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. -# ============================================================================== -# Rustarium Centralized Path Filters -# ============================================================================== -# This file centralizes path filters for change detection in workflows -# and custom actions (evaluated by dorny/paths-filter). -# -# Shared CI/CD Constants (Documented for reference across actions/workflows): -# - Default Python Version: "3.10" -# - Matrix Python Versions (Dev/Main): ["3.10", "3.13"] -# - Matrix Python Versions (Nightly): ["3.10", "3.11", "3.12", "3.13"] -# - Checkout Action Version: "actions/checkout@v4" (v4.2.2 - 11bd71901bbe5b1630ceea73d27597364c9af683) -# ============================================================================== -# ── Change Detection Filters ────────────────────────────────────────────────── -# Evaluated by dorny/paths-filter. Keys correspond to the specific workflow stages. -docs: - - 'docs/**' - - 'zensical.toml' -python_quality: - - 'src/**' - - 'tests/**' - - 'examples/**' - - 'scripts/**' - - 'pyproject.toml' - - 'uv.lock' - - 'Cargo.toml' - - 'Cargo.lock' - - 'crates/**' - - '.github/actions/python/quality/**' -python_security: - - 'src/**' - - 'tests/**' - - 'examples/**' - - 'scripts/**' - - 'pyproject.toml' - - 'uv.lock' - - '.github/actions/python/security/**' -python_tests: - - 'src/**' - - 'tests/**' - - 'examples/**' - - 'scripts/**' - - 'pyproject.toml' - - 'uv.lock' - - 'Cargo.toml' - - 'Cargo.lock' - - 'crates/**' - - '.github/actions/python/tests/**' -python_build: - - 'src/**' - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' +pyproject_changes: - 'pyproject.toml' - 'uv.lock' - - '.github/actions/python/build/**' +github_changes: + - '.github/**' +docs_changes: + - 'docs/**' + - 'examples/**/*.md' + - "*.md" + - "zensical.toml" +examples_changes: + - "examples/**" +scripts_changes: + - "scripts/**/*.py" +src_changes: + - "src/**" + - "crates/**" + - "Cargo.toml" + - "Cargo.lock" +tests_changes: + - "tests/**/*.py" +oci_changes: + - '.devcontainer/**' + - "Dockerfile" + - "docker-compose.yml" + - "cst.yaml" +project_changes: + - "**/*.md" + - "**/*.toml" + - "**/*.txt" + - "**/*.yaml" + - "**/*.yml" + - ".gitignore" + - ".yamllint" rust_quality: - 'crates/**' - 'Cargo.toml' @@ -88,83 +67,3 @@ rust_tests: - 'pyproject.toml' - 'uv.lock' - '.github/actions/rust/tests/**' -rust_build: - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - 'pyproject.toml' - - 'uv.lock' - - '.github/actions/rust/build/**' -oci_quality: - - 'Dockerfile' - - 'docker-compose.yml' - - 'cst.yaml' - - 'pyproject.toml' - - 'uv.lock' - - '.github/**' -oci_security: - - 'Dockerfile' - - 'docker-compose.yml' - - 'cst.yaml' - - 'src/**' - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - 'pyproject.toml' - - 'uv.lock' - - '.github/**' -oci_tests: - - 'Dockerfile' - - 'docker-compose.yml' - - 'cst.yaml' - - 'src/**' - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - 'pyproject.toml' - - 'uv.lock' - - '.github/**' -oci_build: - - 'Dockerfile' - - 'docker-compose.yml' - - 'cst.yaml' - - 'src/**' - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - 'pyproject.toml' - - 'uv.lock' - - '.github/**' -project_quality: - - '**/*.md' - - '**/*.yaml' - - '**/*.yml' - - '**/*.toml' - - 'crates/**' - - 'docs/**' - - 'examples/**' - - 'scripts/**' - - 'src/**' - - 'tests/**' - - '.github/actions/project/quality/**' -project_security: - - '**' -project_tests: - - '**/*.md' - - 'crates/**' - - 'docs/**' - - 'examples/**' - - 'scripts/**' - - 'src/**' - - 'tests/**' - - 'pyproject.toml' - - 'uv.lock' - - '.github/actions/project/tests/**' -project_docs: - - 'docs/**' - - 'zensical.toml' - - 'pyproject.toml' - - 'uv.lock' - - 'src/**' - - 'crates/**' - - '.github/actions/project/docs/**' diff --git a/.github/pipeline-config.json b/.github/pipeline-config.json new file mode 100644 index 0000000..35a1b81 --- /dev/null +++ b/.github/pipeline-config.json @@ -0,0 +1,14 @@ +{ + "default_python": "3.10", + "quick_matrix": [ + "3.10", + "3.14" + ], + "full_matrix": [ + "3.10", + "3.11", + "3.12", + "3.13", + "3.14" + ] +} diff --git a/.github/workflows/pipeline-development.yml b/.github/workflows/pipeline-development.yml index 1df4983..85f42e7 100644 --- a/.github/workflows/pipeline-development.yml +++ b/.github/workflows/pipeline-development.yml @@ -25,210 +25,358 @@ permissions: security-events: write pull-requests: write jobs: - # ── Detect Changes ───────────────────────────────────────────────────────── - changes: - name: "Detect Changes" + + # ── Stage 0: Pipeline Initialization ─────────────────────────────────────── + init-gate: + name: "Pipeline Initialization" runs-on: ubuntu-latest outputs: - docs: ${{ steps.filter.outputs.docs }} + has_changes: "true" + default_python: ${{ steps.parse.outputs.default_python }} + quick_matrix: ${{ steps.parse.outputs.quick_matrix }} + full_matrix: ${{ steps.parse.outputs.full_matrix }} steps: - - name: Checkout repository + - name: Checkout config uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Check for docs changes - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 - id: filter with: - filters: .github/paths-filter.yml + sparse-checkout: .github/pipeline-config.json + sparse-checkout-cone-mode: false + - name: Parse config + id: parse + run: | + echo "default_python=$(jq -r '.default_python' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "quick_matrix=$(jq -c '.quick_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "full_matrix=$(jq -c '.full_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" - # ── Stage 1: Quality Gate ────────────────────────────────────────────────── + # ── Stage 1: Quality Checks ──────────────────────────────────────────────── project-quality: name: "Project Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Project Quality uses: ./.github/actions/project/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} python-quality: name: "Python Quality (Py ${{ matrix.python-version }})" + needs: init-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.quick_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Quality uses: ./.github/actions/python/quality with: python-version: ${{ matrix.python-version }} rust-quality: name: "Rust Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Rust Quality uses: ./.github/actions/rust/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + rust-security: + name: "Rust Security Audit" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Rust Security + uses: ./.github/actions/rust/security + with: + python-version: ${{ needs.init-gate.outputs.default_python }} oci-quality: name: "OCI Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run OCI Quality uses: ./.github/actions/oci/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + quality-gate: + name: "Quality Gate" + needs: + - project-quality + - python-quality + - rust-quality + - rust-security + - oci-quality + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Quality Gate Passed" - # ── Stage 2: Security Audit ──────────────────────────────────────────────── - project-security: - name: "Project Security Audit" + # ── Stage 2: Functional Tests ────────────────────────────────────────────── + project-functional-tests: + name: "Project Functional Tests" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Security - uses: ./.github/actions/project/security - python-security: - name: "Python Security Audit" + with: + fetch-depth: 0 + - name: Run Project Functional Tests + uses: ./.github/actions/project/tests + with: + test-type: "functional" + python-version: ${{ needs.init-gate.outputs.default_python }} + python-functional-tests: + name: "Python Functional Tests (Py ${{ matrix.python-version }})" + needs: init-gate runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJSON(needs.init-gate.outputs.quick_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Python Security - uses: ./.github/actions/python/security with: - python-version: "3.10" - rust-security: - name: "Rust Security Audit" + fetch-depth: 0 + - name: Run Python Functional Tests + id: run-tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-type: "functional" + test-category: "smoke" + - name: Upload Python Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 7 + rust-functional-tests: + name: "Rust Functional Tests" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Security - uses: ./.github/actions/rust/security + with: + fetch-depth: 0 + - name: Run Rust Functional Tests + id: run-tests + uses: ./.github/actions/rust/tests + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + test-type: "functional" + test-category: "smoke" + - name: Upload Rust Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-rust + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 7 + functional-gate: + name: "Functional Gate" + needs: + - project-functional-tests + - python-functional-tests + - rust-functional-tests + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Functional Gate Passed" - # ── Stage 3: Build ───────────────────────────────────────────────────────── - python-build: - name: "Python Build (Py ${{ matrix.python-version }})" + # ── Stage 3: Integrity Checks ────────────────────────────────────────────── + project-link-checks: + name: "Project Link Checks" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Link Checks + uses: ./.github/actions/project/tests + with: + test-type: "link-checks" + python-version: ${{ needs.init-gate.outputs.default_python }} + integrity-gate: + name: "Integrity Gate" needs: - - python-quality - - python-security - - project-quality - - project-security + - project-link-checks + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Integrity Gate Passed" + + # ── Stage 4: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build" + needs: init-gate runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: - - "3.10" - - "3.13" steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Build uses: ./.github/actions/python/build with: - python-version: ${{ matrix.python-version }} + build-type: "dev" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload Build Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-dev-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-dev path: dist/ retention-days: 7 - - name: Create Build Comment Metadata - run: | - mkdir -p dist - echo "📦 **Package Build (Python ${{ matrix.python-version }})**: Wheels and source distributions built successfully." > dist/build-comment.txt - echo "The build artifacts can be downloaded from the [Workflow Run Artifacts Page](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." >> dist/build-comment.txt - - name: Upload Build Comment Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: pr-comment-build-py${{ matrix.python-version }}-${{ github.run_id }} - path: dist/build-comment.txt + oci-build: + name: "OCI Build" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run OCI Build + id: build + uses: ./.github/actions/oci/build + with: + build-type: "dev" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} + - name: Upload OCI Image Tarball + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: build-artifact-oci + path: ${{ steps.build.outputs.image-path }} retention-days: 7 + build-gate: + name: "Build Gate" + needs: + - python-build + - oci-build + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Build Gate Passed" - # ── Stage 4: Tests ───────────────────────────────────────────────────────── - project-tests: - name: "Project Tests (Links)" + # ── Stage 5: E2E Tests ───────────────────────────────────────────────────── + project-e2e-tests: + name: "Project E2E Tests (Examples)" needs: - - project-quality - - project-security + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Tests + with: + fetch-depth: 0 + - name: Run Project E2E Tests uses: ./.github/actions/project/tests with: - test-level: "e2e" - python-tests: - name: "Python Tests (Py ${{ matrix.python-version }})" + test-type: "e2e" + python-version: ${{ needs.init-gate.outputs.default_python }} + python-e2e-tests: + name: "Python E2E Tests (Py ${{ matrix.python-version }})" needs: - - python-build + - init-gate + - build-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.quick_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download Build Artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-dev-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-dev path: dist/ - - name: Run Python Tests + - name: Run Python E2E Tests + id: run-tests uses: ./.github/actions/python/tests with: python-version: ${{ matrix.python-version }} - test-level: "all" + test-type: "e2e" test-category: "smoke" - generate-coverage: "true" - - name: Upload Python Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Upload Python E2E Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id - }} - path: coverage/python/ + name: coverage-e2e-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} retention-days: 7 - rust-tests: - name: "Rust Tests" + oci-e2e-tests: + name: "OCI E2E Tests (Structure)" needs: - - rust-quality - - rust-security + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Tests - uses: ./.github/actions/rust/tests with: - test-level: "all" - test-category: "smoke" - generate-coverage: "true" - - name: Upload Rust Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + fetch-depth: 0 + - name: Download OCI Image Tarball + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: coverage-report-unit-rust-${{ github.run_id }} - path: coverage/rust/ - retention-days: 7 + name: build-artifact-oci + - name: Run OCI E2E Tests + uses: ./.github/actions/oci/tests + with: + image-tar: "rustarium-latest.tar" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} + e2e-gate: + name: "E2E Gate" + needs: + - project-e2e-tests + - python-e2e-tests + - oci-e2e-tests + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "E2E Gate Passed" - # ── Stage 5: Docs (Conditional) ──────────────────────────────────────────── + # ── Stage 6: Docs ────────────────────────────────────────────────────────── docs: name: "Build & Deploy Docs" needs: - - changes - - python-tests - - rust-tests - - project-tests - if: needs.changes.outputs.docs == 'true' && github.event.pull_request.head.repo.fork == - false + - init-gate + - quality-gate + - functional-gate + - e2e-gate + if: always() && needs.init-gate.result == 'success' && github.event.pull_request.head.repo.fork + == false runs-on: ubuntu-latest permissions: contents: write @@ -237,21 +385,23 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + - name: Download Python test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-3.10 + path: coverage/python + merge-multiple: true + continue-on-error: true + - name: Download Rust test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-rust + path: coverage/rust + merge-multiple: true + continue-on-error: true - name: Run Project Docs Builder + id: docs-build uses: ./.github/actions/project/docs with: - build-type: "dev-branch" - alias: "pr-${{ github.event.pull_request.number }}" - include-coverage: "true" - deploy: "true" - - name: Create Docs Comment Metadata - run: | - mkdir -p site - echo "📖 **Documentation Preview**: Staging documentation has been successfully built and deployed." > site/docs-comment.txt - echo "You can view the preview here: [PR Staging Documentation](https://markurtz.github.io/rustarium/pr-${{ github.event.pull_request.number }}/)" >> site/docs-comment.txt - - name: Upload Docs Comment Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: pr-comment-docs-${{ github.run_id }} - path: site/docs-comment.txt - retention-days: 7 + version-name: "pr-${{ github.event.pull_request.number }}" + python-version: ${{ needs.init-gate.outputs.default_python }} diff --git a/.github/workflows/pipeline-main.yml b/.github/workflows/pipeline-main.yml index 808dc8e..57069b8 100644 --- a/.github/workflows/pipeline-main.yml +++ b/.github/workflows/pipeline-main.yml @@ -12,6 +12,34 @@ # 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. + +# ============================================================================== +# PIPELINE: CI — Main +# ------------------------------------------------------------------------------ +# Triggered by: Direct pushes to the 'main' branch. +# +# Part of the Unified Rustarium CI/CD Workflow: +# [ ] 1. DEVELOPMENT (PR validation, preview docs) +# -> [x] 2. MAIN (Post-merge validation, main branch docs) <--- CURRENT WORKFLOW +# [ ] 3. NIGHTLY (Daily check, pre-release publish, nightly docs) +# [ ] 4. WEEKLY (Weekly regression checks) +# [ ] 5. RELEASE (Production PyPI & OCI publish, versioned docs) +# [ ] 6. UTILITY: CLEANUP (Daily stale docs/OCI image cleanup) +# [ ] 7. UTILITY: PR COMMENTER (Coverage & builds reporting on PRs) +# +# High-Level Execution flow & Job Dependencies: +# +# [Pipeline Init] (Stage 0 Gate: init-gate) +# │ +# ├──> [Quality Checks] ───────> [Quality Gate] ───┐ +# ├──> [Functional Tests] ─────> [Functional Gate] ├─> [Main Docs] (Stage 6) +# ├──> [Integrity Checks] ──────> [Integrity Gate] │ (always runs) +# └──> [Build Jobs] ───────────> [Build Gate] │ +# │ │ +# ▼ │ +# [E2E Tests] ────────> [E2E Gate] +# +# ============================================================================== name: "CI — Main" on: push: @@ -22,190 +50,372 @@ concurrency: cancel-in-progress: true permissions: contents: write + packages: write security-events: write jobs: - # ── Stage 1: Quality Gate ────────────────────────────────────────────────── + + # ── Stage 0: Pipeline Initialization ─────────────────────────────────────── + init-gate: + name: "Pipeline Initialization" + runs-on: ubuntu-latest + outputs: + has_changes: "true" + default_python: ${{ steps.parse.outputs.default_python }} + quick_matrix: ${{ steps.parse.outputs.quick_matrix }} + full_matrix: ${{ steps.parse.outputs.full_matrix }} + steps: + - name: Checkout config + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: .github/pipeline-config.json + sparse-checkout-cone-mode: false + - name: Parse config + id: parse + run: | + echo "default_python=$(jq -r '.default_python' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "quick_matrix=$(jq -c '.quick_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "full_matrix=$(jq -c '.full_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + + # ── Stage 1: Quality Checks ──────────────────────────────────────────────── project-quality: name: "Project Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Project Quality uses: ./.github/actions/project/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" python-quality: name: "Python Quality (Py ${{ matrix.python-version }})" + needs: init-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.quick_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Quality uses: ./.github/actions/python/quality with: python-version: ${{ matrix.python-version }} + force-run: "true" rust-quality: name: "Rust Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Rust Quality uses: ./.github/actions/rust/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + rust-security: + name: "Rust Security Audit" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Rust Security + uses: ./.github/actions/rust/security + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" oci-quality: name: "OCI Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run OCI Quality uses: ./.github/actions/oci/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + quality-gate: + name: "Quality Gate" + needs: + - project-quality + - python-quality + - rust-quality + - rust-security + - oci-quality + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Quality Gate Passed" - # ── Stage 2: Security Audit ──────────────────────────────────────────────── - project-security: - name: "Project Security Audit" + # ── Stage 2: Functional Tests ────────────────────────────────────────────── + project-functional-tests: + name: "Project Functional Tests" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Security - uses: ./.github/actions/project/security - python-security: - name: "Python Security Audit" + with: + fetch-depth: 0 + - name: Run Project Functional Tests + uses: ./.github/actions/project/tests + with: + test-type: "functional" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + python-functional-tests: + name: "Python Functional Tests (Py ${{ matrix.python-version }})" + needs: init-gate runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJSON(needs.init-gate.outputs.quick_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Python Security - uses: ./.github/actions/python/security with: - python-version: "3.10" - rust-security: - name: "Rust Security Audit" + fetch-depth: 0 + - name: Run Python Functional Tests + id: run-tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-type: "functional" + test-category: "smoke" + force-run: "true" + - name: Upload Python Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 14 + rust-functional-tests: + name: "Rust Functional Tests" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Security - uses: ./.github/actions/rust/security + with: + fetch-depth: 0 + - name: Run Rust Functional Tests + id: run-tests + uses: ./.github/actions/rust/tests + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + test-type: "functional" + test-category: "smoke" + force-run: "true" + - name: Upload Rust Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-rust + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 14 + functional-gate: + name: "Functional Gate" + needs: + - project-functional-tests + - python-functional-tests + - rust-functional-tests + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Functional Gate Passed" - # ── Stage 3: Build ───────────────────────────────────────────────────────── - python-build: - name: "Python Build (Py ${{ matrix.python-version }})" + # ── Stage 3: Integrity Checks ────────────────────────────────────────────── + project-link-checks: + name: "Project Link Checks" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Link Checks + uses: ./.github/actions/project/tests + with: + test-type: "link-checks" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + integrity-gate: + name: "Integrity Gate" needs: - - python-quality - - python-security - - project-quality - - project-security + - project-link-checks + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Integrity Gate Passed" + + # ── Stage 4: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build" + needs: init-gate runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: - - "3.10" - - "3.13" steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Build uses: ./.github/actions/python/build with: - python-version: ${{ matrix.python-version }} + build-type: "dev" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload Build Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-main-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-main path: dist/ retention-days: 14 + oci-build: + name: "OCI Build" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run OCI Build + id: build + uses: ./.github/actions/oci/build + with: + build-type: "dev" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} + - name: Upload OCI Image Tarball + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: build-artifact-oci + path: ${{ steps.build.outputs.image-path }} + retention-days: 7 + build-gate: + name: "Build Gate" + needs: + - python-build + - oci-build + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Build Gate Passed" - # ── Stage 4: Tests ───────────────────────────────────────────────────────── - project-tests: - name: "Project Tests (Links)" + # ── Stage 5: E2E Tests ───────────────────────────────────────────────────── + project-e2e-tests: + name: "Project E2E Tests (Examples)" needs: - - project-quality - - project-security + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Tests + with: + fetch-depth: 0 + - name: Run Project E2E Tests uses: ./.github/actions/project/tests with: - test-level: "e2e" - python-tests: - name: "Python Tests (Py ${{ matrix.python-version }})" + test-type: "e2e" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} + python-e2e-tests: + name: "Python E2E Tests (Py ${{ matrix.python-version }})" needs: - - python-build + - init-gate + - build-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.quick_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download Build Artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-main-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-main path: dist/ - - name: Run Python Tests + - name: Run Python E2E Tests + id: run-tests uses: ./.github/actions/python/tests with: python-version: ${{ matrix.python-version }} - test-level: "all" - test-category: "smoke or sanity" # Run smoke and sanity tests on main pushes - generate-coverage: "true" - - name: Upload Python Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id - }} - path: coverage/python/ + test-type: "e2e" + test-category: "smoke" + force-run: "true" + - name: Upload Python E2E Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-e2e-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} retention-days: 14 - rust-tests: - name: "Rust Tests" + oci-e2e-tests: + name: "OCI E2E Tests (Structure)" needs: - - rust-quality - - rust-security + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Tests - uses: ./.github/actions/rust/tests with: - test-level: "all" - test-category: "smoke or sanity" - generate-coverage: "true" - - name: Upload Rust Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + fetch-depth: 0 + - name: Download OCI Image Tarball + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: build-artifact-oci + - name: Run OCI E2E Tests + uses: ./.github/actions/oci/tests with: - name: coverage-report-unit-rust-${{ github.run_id }} - path: coverage/rust/ - retention-days: 14 + image-tar: "rustarium-latest.tar" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} + e2e-gate: + name: "E2E Gate" + needs: + - project-e2e-tests + - python-e2e-tests + - oci-e2e-tests + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "E2E Gate Passed" - # ── Stage 5: Docs ────────────────────────────────────────────────────────── + # ── Stage 6: Docs ────────────────────────────────────────────────────────── docs: name: "Build & Deploy Docs" needs: - - project-quality - - python-quality - - rust-quality - - oci-quality - - project-security - - python-security - - rust-security - - project-tests - - python-tests - - rust-tests + - init-gate + - quality-gate + - functional-gate + - e2e-gate + if: always() && needs.init-gate.result == 'success' runs-on: ubuntu-latest permissions: contents: write @@ -214,10 +424,26 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + - name: Get short SHA + id: vars + run: echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + - name: Download Python test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-3.10 + path: coverage/python + merge-multiple: true + continue-on-error: true + - name: Download Rust test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-rust + path: coverage/rust + merge-multiple: true + continue-on-error: true - name: Run Project Docs Builder uses: ./.github/actions/project/docs with: - build-type: "dev" - alias: "latest" - include-coverage: "true" - deploy: "true" + version-name: "main-${{ steps.vars.outputs.short_sha }}" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} diff --git a/.github/workflows/pipeline-nightly.yml b/.github/workflows/pipeline-nightly.yml index 6da4956..9141d30 100644 --- a/.github/workflows/pipeline-nightly.yml +++ b/.github/workflows/pipeline-nightly.yml @@ -12,6 +12,35 @@ # 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. + +# ============================================================================== +# PIPELINE: CI — Nightly +# ------------------------------------------------------------------------------ +# Triggered by: Daily cron scheduled at 00:00 UTC, or manual trigger. +# +# Part of the Unified Rustarium CI/CD Workflow: +# [ ] 1. DEVELOPMENT (PR validation, preview docs) +# [ ] 2. MAIN (Post-merge validation, main branch docs) +# -> [x] 3. NIGHTLY (Daily check, pre-release publish, nightly docs) <--- CURRENT WORKFLOW +# [ ] 4. WEEKLY (Weekly regression checks) +# [ ] 5. RELEASE (Production PyPI & OCI publish, versioned docs) +# [ ] 6. UTILITY: CLEANUP (Daily stale docs/OCI image cleanup) +# [ ] 7. UTILITY: PR COMMENTER (Coverage & builds reporting on PRs) +# +# High-Level Execution flow & Job Dependencies: +# +# [Check Changes & Init] (Stage 0 Gate: init-gate) +# │ +# ▼ (If changes detected) +# ├──> [Quality Checks] ───────> [Quality Gate] ───┐ +# ├──> [Functional Tests] ─────> [Functional Gate] ├─> [Publish Python] ──> [Publish OCI] +# ├──> [Integrity Checks] ──────> [Integrity Gate] │ └─> [Nightly Docs] +# └──> [Build Jobs] ───────────> [Build Gate] │ +# │ │ +# ▼ │ +# [E2E Tests] ────────> [E2E Gate] +# +# ============================================================================== name: "CI — Nightly" on: schedule: @@ -25,12 +54,16 @@ permissions: actions: read attestations: write jobs: - # ── Stage 0: Check Changes ───────────────────────────────────────────────── - check-changes: - name: "Check for Source Changes" + + # ── Stage 0: Pipeline Initialization ─────────────────────────────────────── + init-gate: + name: "Pipeline Initialization" runs-on: ubuntu-latest outputs: has_changes: ${{ steps.check.outputs.has_changes }} + default_python: ${{ steps.parse.outputs.default_python }} + quick_matrix: ${{ steps.parse.outputs.quick_matrix }} + full_matrix: ${{ steps.parse.outputs.full_matrix }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -42,259 +75,380 @@ jobs: if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then echo "has_changes=true" >> "$GITHUB_OUTPUT" else - COMMITS=$(git log --since="24 hours ago" --oneline) + COMMITS=$(git log --since="25 hours ago" --oneline) if [ -z "$COMMITS" ]; then echo "has_changes=false" >> "$GITHUB_OUTPUT" else echo "has_changes=true" >> "$GITHUB_OUTPUT" fi fi + - name: Parse config + id: parse + run: | + echo "default_python=$(jq -r '.default_python' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "quick_matrix=$(jq -c '.quick_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "full_matrix=$(jq -c '.full_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" - # ── Stage 1: Security Audit ──────────────────────────────────────────────── - project-security: - name: "Project Security Audit" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' + # ── Stage 1: Quality Checks ──────────────────────────────────────────────── + project-quality: + name: "Project Quality" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Quality + uses: ./.github/actions/project/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + python-quality: + name: "Python Quality (Py ${{ matrix.python-version }})" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Security - uses: ./.github/actions/project/security with: + fetch-depth: 0 + - name: Run Python Quality + uses: ./.github/actions/python/quality + with: + python-version: ${{ matrix.python-version }} force-run: "true" - python-security: - name: "Python Security Audit" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' + rust-quality: + name: "Rust Quality" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Python Security - uses: ./.github/actions/python/security with: - python-version: "3.10" + fetch-depth: 0 + - name: Run Rust Quality + uses: ./.github/actions/rust/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" rust-security: name: "Rust Security Audit" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Rust Security uses: ./.github/actions/rust/security with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - oci-security: - name: "OCI Security Audit" - needs: check-changes - if: needs.check-changes.outputs.has_changes == 'true' + oci-quality: + name: "OCI Quality" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run OCI Security - uses: ./.github/actions/oci/security with: + fetch-depth: 0 + - name: Run OCI Quality + uses: ./.github/actions/oci/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - - # ── Stage 2: Build ───────────────────────────────────────────────────────── - python-build: - name: "Python Build (Py ${{ matrix.python-version }})" + quality-gate: + name: "Quality Gate" needs: - - check-changes - - python-security - - project-security - if: needs.check-changes.outputs.has_changes == 'true' + - init-gate + - project-quality + - python-quality + - rust-quality + - rust-security + - oci-quality + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Quality Gate Passed" + + # ── Stage 2: Functional Tests ────────────────────────────────────────────── + project-functional-tests: + name: "Project Functional Tests" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Functional Tests + uses: ./.github/actions/project/tests + with: + test-type: "functional" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + python-functional-tests: + name: "Python Functional Tests (Py ${{ matrix.python-version }})" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Python Functional Tests + id: run-tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-type: "functional" + test-category: "all" + force-run: "true" + - name: Upload Python Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 14 + rust-functional-tests: + name: "Rust Functional Tests" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Rust Functional Tests + id: run-tests + uses: ./.github/actions/rust/tests + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + test-type: "functional" + test-category: "all" + force-run: "true" + - name: Upload Rust Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-rust + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 14 + functional-gate: + name: "Functional Gate" + needs: + - init-gate + - project-functional-tests + - python-functional-tests + - rust-functional-tests + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Functional Gate Passed" + + # ── Stage 3: Integrity Checks ────────────────────────────────────────────── + project-link-checks: + name: "Project Link Checks" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Link Checks + uses: ./.github/actions/project/tests + with: + test-type: "link-checks" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + integrity-gate: + name: "Integrity Gate" + needs: + - init-gate + - project-link-checks + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Integrity Gate Passed" + + # ── Stage 4: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Build uses: ./.github/actions/python/build with: - python-version: ${{ matrix.python-version }} + build-type: "nightly" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload Build Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-nightly-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-nightly path: dist/ retention-days: 7 oci-build: name: "OCI Build" - needs: - - check-changes - - oci-security - - project-security - if: needs.check-changes.outputs.has_changes == 'true' + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run OCI Build + id: build uses: ./.github/actions/oci/build with: - save-image: "true" + build-type: "nightly" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload OCI Image Tarball - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-oci-nightly-${{ github.run_id }} - path: rustarium-latest.tar + name: build-artifact-oci-nightly + path: ${{ steps.build.outputs.image-path }} retention-days: 7 - - # ── Stage 3: Tests ───────────────────────────────────────────────────────── - project-tests: - name: "Project Tests (Doc Tests)" + build-gate: + name: "Build Gate" needs: - - check-changes - - project-security - if: needs.check-changes.outputs.has_changes == 'true' + - init-gate + - python-build + - oci-build + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Doc Tests - uses: ./.github/actions/project/tests - with: - test-level: "integration" - force-run: "true" + - name: Success + run: echo "Build Gate Passed" + + # ── Stage 5: E2E Tests ───────────────────────────────────────────────────── project-e2e-tests: - name: "Project E2E Tests" + name: "Project E2E Tests (Examples)" needs: - - check-changes - - project-security - if: needs.check-changes.outputs.has_changes == 'true' + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Project E2E Tests uses: ./.github/actions/project/tests with: - test-level: "e2e" + test-type: "e2e" force-run: "true" - python-tests: - name: "Python Tests (Py ${{ matrix.python-version }})" + python-version: ${{ needs.init-gate.outputs.default_python }} + python-e2e-tests: + name: "Python E2E Tests (Py ${{ matrix.python-version }})" needs: - - python-build + - init-gate + - build-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download Build Artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-nightly-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-nightly path: dist/ - - name: Run Python Tests + - name: Run Python E2E Tests + id: run-tests uses: ./.github/actions/python/tests with: python-version: ${{ matrix.python-version }} - test-level: "all" - test-category: "regression" # Run deep regression tests nightly - generate-coverage: "true" + test-type: "e2e" + test-category: "smoke or sanity" force-run: "true" - - name: Upload Python Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Upload Python E2E Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id - }} - path: coverage/python/ + name: coverage-e2e-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} retention-days: 14 - rust-tests: - name: "Rust Tests" + oci-e2e-tests: + name: "OCI E2E Tests (Structure)" needs: - - check-changes - - rust-security - if: needs.check-changes.outputs.has_changes == 'true' + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Tests - uses: ./.github/actions/rust/tests - with: - test-level: "all" - test-category: "regression" - generate-coverage: "true" - force-run: "true" - - name: Upload Rust Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: coverage-report-unit-rust-${{ github.run_id }} - path: coverage/rust/ - retention-days: 14 - oci-tests: - name: "OCI Tests" - needs: - - oci-build - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + fetch-depth: 0 - name: Download OCI Image Tarball - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-oci-nightly-${{ github.run_id }} - - name: Run OCI Structure Tests + name: build-artifact-oci-nightly + - name: Run OCI E2E Tests uses: ./.github/actions/oci/tests with: - test-level: "e2e" + image-tar: "rustarium-latest.tar" force-run: "true" - - # ── Stage 4: Docs ────────────────────────────────────────────────────────── - docs: - name: "Build & Deploy Docs" + python-version: ${{ needs.init-gate.outputs.default_python }} + e2e-gate: + name: "E2E Gate" needs: - - check-changes - - python-tests - - rust-tests - - project-tests - project-e2e-tests - if: needs.check-changes.outputs.has_changes == 'true' + - python-e2e-tests + - oci-e2e-tests + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest - permissions: - contents: write steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - name: Run Project Docs Builder - uses: ./.github/actions/project/docs - with: - build-type: "nightly" - alias: "nightly" - include-coverage: "true" - deploy: "true" - force-run: "true" + - name: Success + run: echo "E2E Gate Passed" - # ── Stage 5: Publish ─────────────────────────────────────────────────────── + # ── Stage 6: Publish & Deploy ────────────────────────────────────────────── publish-python: name: "Publish Nightly Python Packages" needs: - - python-tests + - init-gate + - quality-gate + - functional-gate + - e2e-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest permissions: contents: write @@ -303,34 +457,80 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download All Built Wheels - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - pattern: build-artifact-nightly-py* + name: build-artifact-nightly path: dist/ - merge-multiple: true - name: Publish Python Package uses: ./.github/actions/python/publish with: - artifact-name: "" create-release: "false" publish-oci: name: "Publish Nightly OCI Image" needs: - - oci-tests + - init-gate + - publish-python + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest permissions: packages: write steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Get commit info + id: vars + run: | + echo "date=$(git log -1 --format=%cd --date=format:%Y%m%d)" >> "$GITHUB_OUTPUT" - name: Download OCI Image Tarball - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-oci-nightly-${{ github.run_id }} + name: build-artifact-oci-nightly - name: Publish OCI Image uses: ./.github/actions/oci/publish with: - image-tag: "nightly" - github-token: ${{ secrets.GITHUB_TOKEN }} - load-image: "true" + image-tar: "rustarium-latest.tar" + image-tag: "nightly-${{ steps.vars.outputs.date }}" + alias-tag: "nightly" + docs: + name: "Build & Deploy Docs" + needs: + - init-gate + - publish-python + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Get commit info + id: vars + run: | + echo "date=$(git log -1 --format=%cd --date=format:%Y%m%d)" >> "$GITHUB_OUTPUT" + - name: Download Python test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-3.10 + path: coverage/python + merge-multiple: true + continue-on-error: true + - name: Download Rust test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-rust + path: coverage/rust + merge-multiple: true + continue-on-error: true + - name: Run Project Docs Builder + uses: ./.github/actions/project/docs + with: + version-name: "nightly-${{ steps.vars.outputs.date }}" + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} diff --git a/.github/workflows/pipeline-release.yml b/.github/workflows/pipeline-release.yml index 1f10a84..fae0089 100644 --- a/.github/workflows/pipeline-release.yml +++ b/.github/workflows/pipeline-release.yml @@ -12,6 +12,34 @@ # 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. + +# ============================================================================== +# PIPELINE: CI — Release +# ------------------------------------------------------------------------------ +# Triggered by: Direct push of a semver tag (v*.*.*). +# +# Part of the Unified Rustarium CI/CD Workflow: +# [ ] 1. DEVELOPMENT (PR validation, preview docs) +# [ ] 2. MAIN (Post-merge validation, main branch docs) +# [ ] 3. NIGHTLY (Daily check, pre-release publish, nightly docs) +# [ ] 4. WEEKLY (Weekly regression checks) +# -> [x] 5. RELEASE (Production PyPI & OCI publish, versioned docs) <--- CURRENT WORKFLOW +# [ ] 6. UTILITY: CLEANUP (Daily stale docs/OCI image cleanup) +# [ ] 7. UTILITY: PR COMMENTER (Coverage & builds reporting on PRs) +# +# High-Level Execution flow & Job Dependencies: +# +# [Pipeline Init] (Stage 0 Gate: init-gate) +# │ +# ├──> [Quality Checks] ───────> [Quality Gate] ───┐ +# ├──> [Functional Tests] ─────> [Functional Gate] ├─> [Publish Python] ──> [Publish OCI] +# ├──> [Integrity Checks] ──────> [Integrity Gate] │ └─> [Versioned Docs] +# └──> [Build Jobs] ───────────> [Build Gate] │ +# │ │ +# ▼ │ +# [E2E Tests] ────────> [E2E Gate] +# +# ============================================================================== name: "CI — Release" on: push: @@ -24,234 +52,367 @@ permissions: security-events: write attestations: write jobs: - # ── Stage 1: Security Audit ──────────────────────────────────────────────── - project-security: - name: "Project Security Audit" + + # ── Stage 0: Pipeline Initialization ─────────────────────────────────────── + init-gate: + name: "Pipeline Initialization" + runs-on: ubuntu-latest + outputs: + has_changes: "true" + default_python: ${{ steps.parse.outputs.default_python }} + quick_matrix: ${{ steps.parse.outputs.quick_matrix }} + full_matrix: ${{ steps.parse.outputs.full_matrix }} + steps: + - name: Checkout config + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: .github/pipeline-config.json + sparse-checkout-cone-mode: false + - name: Parse config + id: parse + run: | + echo "default_python=$(jq -r '.default_python' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "quick_matrix=$(jq -c '.quick_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "full_matrix=$(jq -c '.full_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + + # ── Stage 1: Quality Checks ──────────────────────────────────────────────── + project-quality: + name: "Project Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Security - uses: ./.github/actions/project/security with: + fetch-depth: 0 + - name: Run Project Quality + uses: ./.github/actions/project/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - python-security: - name: "Python Security Audit" + python-quality: + name: "Python Quality (Py ${{ matrix.python-version }})" + needs: init-gate runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Python Security - uses: ./.github/actions/python/security with: - python-version: "3.10" + fetch-depth: 0 + - name: Run Python Quality + uses: ./.github/actions/python/quality + with: + python-version: ${{ matrix.python-version }} + force-run: "true" + rust-quality: + name: "Rust Quality" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Rust Quality + uses: ./.github/actions/rust/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" rust-security: name: "Rust Security Audit" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Rust Security uses: ./.github/actions/rust/security with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - oci-security: - name: "OCI Security Audit" + oci-quality: + name: "OCI Quality" + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run OCI Security - uses: ./.github/actions/oci/security with: + fetch-depth: 0 + - name: Run OCI Quality + uses: ./.github/actions/oci/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - - # ── Stage 2: Build ───────────────────────────────────────────────────────── - python-build: - name: "Python Build (Py ${{ matrix.python-version }})" + quality-gate: + name: "Quality Gate" needs: - - python-security - - project-security + - project-quality + - python-quality + - rust-quality + - rust-security + - oci-quality + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Quality Gate Passed" + + # ── Stage 2: Functional Tests ────────────────────────────────────────────── + project-functional-tests: + name: "Project Functional Tests" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Functional Tests + uses: ./.github/actions/project/tests + with: + test-type: "functional" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + python-functional-tests: + name: "Python Functional Tests (Py ${{ matrix.python-version }})" + needs: init-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Python Functional Tests + id: run-tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-type: "functional" + test-category: "regression" + force-run: "true" + - name: Upload Python Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 90 + rust-functional-tests: + name: "Rust Functional Tests" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Rust Functional Tests + id: run-tests + uses: ./.github/actions/rust/tests + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + test-type: "functional" + test-category: "regression" + force-run: "true" + - name: Upload Rust Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-rust + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 90 + functional-gate: + name: "Functional Gate" + needs: + - project-functional-tests + - python-functional-tests + - rust-functional-tests + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Functional Gate Passed" + + # ── Stage 3: Integrity Checks ────────────────────────────────────────────── + project-link-checks: + name: "Project Link Checks" + needs: init-gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Link Checks + uses: ./.github/actions/project/tests + with: + test-type: "link-checks" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + integrity-gate: + name: "Integrity Gate" + needs: + - project-link-checks + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Integrity Gate Passed" + + # ── Stage 4: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build" + needs: init-gate + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Build uses: ./.github/actions/python/build with: - python-version: ${{ matrix.python-version }} + build-type: "release" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload Build Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-release-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-release path: dist/ retention-days: 7 oci-build: name: "OCI Build" - needs: - - oci-security - - project-security + needs: init-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run OCI Build + id: build uses: ./.github/actions/oci/build with: - save-image: "true" + build-type: "release" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload OCI Image Tarball - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-oci-release-${{ github.run_id }} - path: rustarium-latest.tar + name: build-artifact-oci-release + path: ${{ steps.build.outputs.image-path }} retention-days: 7 - - # ── Stage 3: Tests ───────────────────────────────────────────────────────── - project-tests: - name: "Project Tests (Doc Tests)" + build-gate: + name: "Build Gate" needs: - - project-security + - python-build + - oci-build runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Tests - uses: ./.github/actions/project/tests - with: - test-level: "integration" - force-run: "true" + - name: Success + run: echo "Build Gate Passed" + + # ── Stage 5: E2E Tests ───────────────────────────────────────────────────── project-e2e-tests: - name: "Project E2E Tests" + name: "Project E2E Tests (Examples)" needs: - - project-security + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Project E2E Tests uses: ./.github/actions/project/tests with: - test-level: "e2e" + test-type: "e2e" force-run: "true" - python-tests: - name: "Python Tests (Py ${{ matrix.python-version }})" + python-version: ${{ needs.init-gate.outputs.default_python }} + python-e2e-tests: + name: "Python E2E Tests (Py ${{ matrix.python-version }})" needs: - - python-build + - init-gate + - build-gate runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download Build Artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-release-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-release path: dist/ - - name: Run Python Tests + - name: Run Python E2E Tests + id: run-tests uses: ./.github/actions/python/tests with: python-version: ${{ matrix.python-version }} - test-level: "all" - test-category: "regression" # Run deep regression tests for releases - generate-coverage: "true" - force-run: "true" - - name: Upload Python Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id - }} - path: coverage/python/ - retention-days: 90 - rust-tests: - name: "Rust Tests" - needs: - - rust-security - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Tests - uses: ./.github/actions/rust/tests - with: - test-level: "all" + test-type: "e2e" test-category: "regression" - generate-coverage: "true" force-run: "true" - - name: Upload Rust Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Upload Python E2E Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: coverage-report-unit-rust-${{ github.run_id }} - path: coverage/rust/ + name: coverage-e2e-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} retention-days: 90 - oci-tests: - name: "OCI Tests" + oci-e2e-tests: + name: "OCI E2E Tests (Structure)" needs: - - oci-build + - init-gate + - build-gate runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download OCI Image Tarball - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-oci-release-${{ github.run_id }} - - name: Run OCI Structure Tests + name: build-artifact-oci-release + - name: Run OCI E2E Tests uses: ./.github/actions/oci/tests with: - test-level: "e2e" + image-tar: "rustarium-latest.tar" force-run: "true" - - # ── Stage 4: Versioned Docs ──────────────────────────────────────────────── - docs: - name: "Versioned Docs" + python-version: ${{ needs.init-gate.outputs.default_python }} + e2e-gate: + name: "E2E Gate" needs: - - python-tests - - rust-tests - - project-tests - project-e2e-tests + - python-e2e-tests + - oci-e2e-tests runs-on: ubuntu-latest - permissions: - contents: write steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - name: Run Project Docs Builder - uses: ./.github/actions/project/docs - with: - build-type: "release" - alias: "latest" - include-coverage: "true" - deploy: "true" - force-run: "true" + - name: Success + run: echo "E2E Gate Passed" - # ── Stage 5: Publish Artifacts ───────────────────────────────────────────── + # ── Stage 6: Publish & Deploy ────────────────────────────────────────────── publish-python: name: "Publish Python Release" needs: - - python-tests - - rust-tests - - project-e2e-tests - - oci-tests + - quality-gate + - functional-gate + - e2e-gate runs-on: ubuntu-latest permissions: contents: write @@ -260,35 +421,69 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download All Built Wheels - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - pattern: build-artifact-release-py* + name: build-artifact-release path: dist/ - merge-multiple: true - name: Publish Python Package uses: ./.github/actions/python/publish with: - artifact-name: "" # Not needed since we downloaded all wheels directly to dist/ create-release: "true" - github-token: ${{ secrets.GITHUB_TOKEN }} publish-oci: name: "Publish Release OCI Image" needs: - - oci-tests + - publish-python runs-on: ubuntu-latest permissions: packages: write steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download OCI Image Tarball - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-oci-release-${{ github.run_id }} + name: build-artifact-oci-release - name: Publish OCI Image uses: ./.github/actions/oci/publish with: + image-tar: "rustarium-latest.tar" image-tag: ${{ github.ref_name }} - github-token: ${{ secrets.GITHUB_TOKEN }} - load-image: "true" + alias-tag: "latest" + docs: + name: "Versioned Docs" + needs: + - init-gate + - publish-python + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Download Python test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-3.10 + path: coverage/python + merge-multiple: true + continue-on-error: true + - name: Download Rust test coverage + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-*-rust + path: coverage/rust + merge-multiple: true + continue-on-error: true + - name: Run Project Docs Builder + uses: ./.github/actions/project/docs + with: + version-name: ${{ github.ref_name }} + force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} diff --git a/.github/workflows/pipeline-weekly.yml b/.github/workflows/pipeline-weekly.yml index c73007b..3663923 100644 --- a/.github/workflows/pipeline-weekly.yml +++ b/.github/workflows/pipeline-weekly.yml @@ -12,6 +12,32 @@ # 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. + +# ============================================================================== +# PIPELINE: CI — Weekly +# ------------------------------------------------------------------------------ +# Triggered by: Weekly cron scheduled at 00:00 UTC on Sundays, or manual trigger. +# +# Part of the Unified Rustarium CI/CD Workflow: +# [ ] 1. DEVELOPMENT (PR validation, preview docs) +# [ ] 2. MAIN (Post-merge validation, main branch docs) +# [ ] 3. NIGHTLY (Daily check, pre-release publish, nightly docs) +# -> [x] 4. WEEKLY (Weekly regression checks) <--- CURRENT WORKFLOW +# [ ] 5. RELEASE (Production PyPI & OCI publish, versioned docs) +# [ ] 6. UTILITY: CLEANUP (Daily stale docs/OCI image cleanup) +# [ ] 7. UTILITY: PR COMMENTER (Coverage & builds reporting on PRs) +# +# High-Level Execution flow & Job Dependencies: +# +# [Check Changes & Init] (Stage 0 Gate: init-gate) +# │ +# ▼ (If changes detected) +# ├──> [Quality Checks] ───────> [Quality Gate] ───┐ +# ├──> [Functional Tests] ─────> [Functional Gate] ├─> [E2E Tests] ────────> [E2E Gate] +# ├──> [Integrity Checks] ──────> [Integrity Gate] │ +# └──> [Build Jobs] ───────────> [Build Gate] ──────┘ +# +# ============================================================================== name: "CI — Weekly" on: schedule: @@ -21,197 +47,402 @@ permissions: contents: read security-events: write jobs: - # ── Stage 1: Security Audit ──────────────────────────────────────────────── - project-security: - name: "Project Security Audit" + + # ── Stage 0: Pipeline Initialization ─────────────────────────────────────── + init-gate: + name: "Pipeline Initialization" runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check.outputs.has_changes }} + default_python: ${{ steps.parse.outputs.default_python }} + quick_matrix: ${{ steps.parse.outputs.quick_matrix }} + full_matrix: ${{ steps.parse.outputs.full_matrix }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Security - uses: ./.github/actions/project/security with: + fetch-depth: 0 + - name: Check for recent commits + id: check + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + else + COMMITS=$(git log --since="8 days ago" --oneline) + if [ -z "$COMMITS" ]; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + echo "has_changes=true" >> "$GITHUB_OUTPUT" + fi + fi + - name: Parse config + id: parse + run: | + echo "default_python=$(jq -r '.default_python' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "quick_matrix=$(jq -c '.quick_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + echo "full_matrix=$(jq -c '.full_matrix' .github/pipeline-config.json)" >> "$GITHUB_OUTPUT" + + # ── Stage 1: Quality Checks ──────────────────────────────────────────────── + project-quality: + name: "Project Quality" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Quality + uses: ./.github/actions/project/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - python-security: - name: "Python Security Audit" + python-quality: + name: "Python Quality (Py ${{ matrix.python-version }})" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Python Quality + uses: ./.github/actions/python/quality + with: + python-version: ${{ matrix.python-version }} + force-run: "true" + rust-quality: + name: "Rust Quality" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Python Security - uses: ./.github/actions/python/security with: - python-version: "3.10" + fetch-depth: 0 + - name: Run Rust Quality + uses: ./.github/actions/rust/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" rust-security: name: "Rust Security Audit" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Rust Security uses: ./.github/actions/rust/security with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" - oci-security: - name: "OCI Security Audit" + oci-quality: + name: "OCI Quality" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run OCI Security - uses: ./.github/actions/oci/security with: + fetch-depth: 0 + - name: Run OCI Quality + uses: ./.github/actions/oci/quality + with: + python-version: ${{ needs.init-gate.outputs.default_python }} force-run: "true" + quality-gate: + name: "Quality Gate" + needs: + - init-gate + - project-quality + - python-quality + - rust-quality + - rust-security + - oci-quality + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Quality Gate Passed" - # ── Stage 2: Build ───────────────────────────────────────────────────────── - python-build: - name: "Python Build (Py ${{ matrix.python-version }})" + # ── Stage 2: Functional Tests ────────────────────────────────────────────── + project-functional-tests: + name: "Project Functional Tests" + needs: + - init-gate + - project-quality + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Functional Tests + uses: ./.github/actions/project/tests + with: + test-type: "functional" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + python-functional-tests: + name: "Python Functional Tests (Py ${{ matrix.python-version }})" needs: - - python-security - - project-security + - init-gate + - python-quality + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Python Functional Tests + id: run-tests + uses: ./.github/actions/python/tests + with: + python-version: ${{ matrix.python-version }} + test-type: "functional" + test-category: "regression" + force-run: "true" + - name: Upload Python Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 14 + rust-functional-tests: + name: "Rust Functional Tests" + needs: + - init-gate + - rust-quality + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Rust Functional Tests + id: run-tests + uses: ./.github/actions/rust/tests + with: + python-version: ${{ needs.init-gate.outputs.default_python }} + test-type: "functional" + test-category: "regression" + force-run: "true" + - name: Upload Rust Functional Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-functional-rust + path: ${{ steps.run-tests.outputs.coverage-reports }} + retention-days: 14 + functional-gate: + name: "Functional Gate" + needs: + - init-gate + - project-functional-tests + - python-functional-tests + - rust-functional-tests + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Functional Gate Passed" + + # ── Stage 3: Integrity Checks ────────────────────────────────────────────── + project-link-checks: + name: "Project Link Checks" + needs: + - init-gate + - project-quality + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Run Project Link Checks + uses: ./.github/actions/project/tests + with: + test-type: "link-checks" + python-version: ${{ needs.init-gate.outputs.default_python }} + force-run: "true" + integrity-gate: + name: "Integrity Gate" + needs: + - init-gate + - project-link-checks + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "Integrity Gate Passed" + + # ── Stage 4: Build ───────────────────────────────────────────────────────── + python-build: + name: "Python Build" + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Python Build uses: ./.github/actions/python/build with: - python-version: ${{ matrix.python-version }} + build-type: "dev" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload Build Artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-weekly-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-weekly path: dist/ retention-days: 7 oci-build: name: "OCI Build" - needs: - - oci-security - - project-security + needs: init-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run OCI Build + id: build uses: ./.github/actions/oci/build with: - save-image: "true" + build-type: "dev" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} - name: Upload OCI Image Tarball - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-artifact-oci-weekly-${{ github.run_id }} - path: rustarium-latest.tar + name: build-artifact-oci-weekly + path: ${{ steps.build.outputs.image-path }} retention-days: 7 - - # ── Stage 3: Tests ───────────────────────────────────────────────────────── - project-tests: - name: "Project Tests (Doc Tests)" + build-gate: + name: "Build Gate" needs: - - project-security + - init-gate + - python-build + - oci-build + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Project Tests - uses: ./.github/actions/project/tests - with: - test-level: "integration" - force-run: "true" + - name: Success + run: echo "Build Gate Passed" + + # ── Stage 5: E2E Tests ───────────────────────────────────────────────────── project-e2e-tests: - name: "Project E2E Tests" + name: "Project E2E Tests (Examples)" needs: - - project-security + - init-gate + - project-quality + - build-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Run Project E2E Tests uses: ./.github/actions/project/tests with: - test-level: "e2e" + test-type: "e2e" force-run: "true" - python-tests: - name: "Python Tests (Py ${{ matrix.python-version }})" + python-version: ${{ needs.init-gate.outputs.default_python }} + python-e2e-tests: + name: "Python E2E Tests (Py ${{ matrix.python-version }})" needs: + - init-gate - python-build + - build-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" + python-version: ${{ fromJSON(needs.init-gate.outputs.full_matrix) }} steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download Build Artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-weekly-py${{ matrix.python-version }}-${{ github.run_id }} + name: build-artifact-weekly path: dist/ - - name: Run Python Tests + - name: Run Python E2E Tests + id: run-tests uses: ./.github/actions/python/tests with: python-version: ${{ matrix.python-version }} - test-level: "all" - test-category: "regression" - generate-coverage: "true" - force-run: "true" - - name: Upload Python Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: coverage-report-unit-python-py${{ matrix.python-version }}-${{ github.run_id - }} - path: coverage/python/ - retention-days: 14 - rust-tests: - name: "Rust Tests" - needs: - - rust-security - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Run Rust Tests - uses: ./.github/actions/rust/tests - with: - test-level: "all" + test-type: "e2e" test-category: "regression" - generate-coverage: "true" force-run: "true" - - name: Upload Rust Coverage Report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Upload Python E2E Coverage Report + if: steps.run-tests.outputs.coverage-reports != '' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: coverage-report-unit-rust-${{ github.run_id }} - path: coverage/rust/ + name: coverage-e2e-${{ matrix.python-version }} + path: ${{ steps.run-tests.outputs.coverage-reports }} retention-days: 14 - oci-tests: - name: "OCI Tests" + oci-e2e-tests: + name: "OCI E2E Tests (Structure)" needs: + - init-gate - oci-build + - build-gate + if: needs.init-gate.outputs.has_changes == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 - name: Download OCI Image Tarball - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: build-artifact-oci-weekly-${{ github.run_id }} - - name: Run OCI Structure Tests + name: build-artifact-oci-weekly + - name: Run OCI E2E Tests uses: ./.github/actions/oci/tests with: - test-level: "e2e" + image-tar: "rustarium-latest.tar" force-run: "true" + python-version: ${{ needs.init-gate.outputs.default_python }} + e2e-gate: + name: "E2E Gate" + needs: + - project-e2e-tests + - python-e2e-tests + - oci-e2e-tests + if: needs.init-gate.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "E2E Gate Passed" diff --git a/.github/workflows/util-cleanup.yml b/.github/workflows/util-cleanup.yml new file mode 100644 index 0000000..c9dc6dc --- /dev/null +++ b/.github/workflows/util-cleanup.yml @@ -0,0 +1,133 @@ +--- +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================== +# PIPELINE: CI — Repository Cleanup +# ------------------------------------------------------------------------------ +# Triggered by: Pull Requests closed, daily cron at 00:00 UTC, or manual trigger. +# +# Part of the Unified Rustarium CI/CD Workflow: +# [ ] 1. DEVELOPMENT (PR validation, preview docs) +# [ ] 2. MAIN (Post-merge validation, main branch docs) +# [ ] 3. NIGHTLY (Daily check, pre-release publish, nightly docs) +# [ ] 4. WEEKLY (Weekly regression checks) +# [ ] 5. RELEASE (Production PyPI & OCI publish, versioned docs) +# -> [x] 6. UTILITY: CLEANUP (Daily stale docs/OCI image cleanup) <--- CURRENT WORKFLOW +# [ ] 7. UTILITY: PR COMMENTER (Coverage & builds reporting on PRs) +# +# High-Level Execution flow & Job Dependencies: +# +# (Runs in parallel depending on the trigger event) +# ├──> [cleanup-docs] (Deletes preview review/pr-* docs and stale gh-pages branches) +# └──> [cleanup-oci] (Deletes old containers from registry, keeping last N containers) +# +# ============================================================================== +name: "CI — Repository Cleanup" +on: + pull_request: + types: + - closed + schedule: + - cron: "0 0 * * *" + workflow_dispatch: +permissions: + contents: write + packages: write +jobs: + cleanup-docs: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == + false + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Setup Git Identity + uses: ./.github/actions/utility/setup-git + - name: Set up Python & Hatch + uses: ./.github/actions/utility/setup-python + with: + python-version: "3.10" + - name: Cleanup Versioned Docs (Mike) + if: github.event_name != 'pull_request' + run: | + python3 -c ' + import json, re, subprocess, pathlib + res = subprocess.run(["git", "show", "origin/gh-pages:versions.json"], capture_output=True, text=True) + versions = json.loads(res.stdout) if res.returncode == 0 else [] + active = {v["version"] for v in versions if any(a in v.get("aliases", []) for a in ("dev", "nightly", "latest"))} + to_delete = [v["version"] for v in versions if (v["version"].startswith("main-") or re.match(r"^\d+\.\d+\.\d+\.a\d{8}$", v["version"])) and v["version"] not in active] + if to_delete: subprocess.run(["hatch", "run", "mike", "delete", "-F", "zensical.toml", "--push"] + to_delete, check=True) + ' + - name: Switch to gh-pages branch + run: | + git fetch origin gh-pages + git checkout -f gh-pages + - name: Cleanup Stale PRs + env: + PR_NUM: "${{ github.event.pull_request.number }}" + run: | + python3 -c ' + import os, subprocess, shutil, pathlib + review_dir = pathlib.Path("review") + if not review_dir.exists(): exit(0) + pr_num = os.environ.get("PR_NUM") + dirs = [review_dir / f"pr-{pr_num}"] if pr_num and pr_num != "" else [p for p in review_dir.glob("pr-*") if p.is_dir()] + for d in dirs: + if d.exists(): + shutil.rmtree(d) + subprocess.run(["git", "rm", "-rf", str(d)], check=False) + ' + - name: Commit and Push Changes + run: | + if [ -n "$(git status --porcelain)" ]; then + git commit -m "docs: cleanup stale PR previews and versions" + git push origin gh-pages + fi + cleanup-oci: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + steps: + - name: Cleanup OCI Images + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{ github.token }} + script: |- + const owner = context.repo.owner; + const pkg = 'rustarium'; + const fetchVersions = async (fn) => { + try { + return (await fn({ org: owner, package_type: 'container', package_name: pkg, per_page: 100 })).data; + } catch { + return (await fn({ username: owner, package_type: 'container', package_name: pkg, per_page: 100 })).data; + } + }; + const versions = await fetchVersions(github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg); + const isNightly = (v) => (v.metadata?.container?.tags || []).some(t => t.startsWith('nightly-')); + const isRelease = (v) => (v.metadata?.container?.tags || []).some(t => /^v\d+\.\d+\.\d+/.test(t)); + const nightly = versions.filter(isNightly).sort((a,b) => new Date(b.created_at) - new Date(a.created_at)); + const release = versions.filter(isRelease).sort((a,b) => new Date(b.created_at) - new Date(a.created_at)); + for (const ver of [...nightly.slice(5), ...release.slice(30)]) { + try { + await github.rest.packages.deletePackageVersionForOrg({ org: owner, package_type: 'container', package_name: pkg, package_version_id: ver.id }); + } catch { + try { + await github.rest.packages.deletePackageVersionForUser({ username: owner, package_type: 'container', package_name: pkg, package_version_id: ver.id }); + } catch (err) { + console.error(`Failed to delete version ${ver.id}:`, err); + } + } + } diff --git a/.github/workflows/util-development-cleanup.yml b/.github/workflows/util-development-cleanup.yml deleted file mode 100644 index d30f427..0000000 --- a/.github/workflows/util-development-cleanup.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -# ============================================================================= -# util-development-cleanup.yml -# -# Triggers: -# pull_request → closed (merged or abandoned) -# -# Purpose: -# Deletes the ephemeral documentation preview deployed to GitHub Pages -# during the PR lifecycle (e.g., pr-123/) to prevent gh-pages branch bloat. -# ============================================================================= -name: "CI — Development Cleanup" -on: - pull_request: - types: - - closed -permissions: - contents: write # Required to push deletion to gh-pages -jobs: - cleanup-pr-docs: - name: "Delete PR Docs Preview" - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Configure Git Credentials - run: | - git config user.name github-actions[bot] - git config user.email github-actions[bot]@users.noreply.github.com - - name: Delete PR docs preview - run: |- - PR_PATH="pr-${{ github.event.pull_request.number }}" - echo "Deleting docs preview at path: ${PR_PATH}" - git fetch origin gh-pages:gh-pages || true - git checkout gh-pages || exit 0 - if [ -d "$PR_PATH" ]; then - git rm -rf "$PR_PATH" - git commit -m "docs: cleanup ephemeral docs preview for $PR_PATH" || echo "No changes to commit" - git push origin gh-pages - else - echo "No docs preview found for $PR_PATH, nothing to clean up." - fi diff --git a/.github/workflows/util-pr-comment.yml b/.github/workflows/util-pr-comment.yml index 2c58a0f..f327240 100644 --- a/.github/workflows/util-pr-comment.yml +++ b/.github/workflows/util-pr-comment.yml @@ -13,17 +13,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -# ============================================================================= -# util-pr-comment.yml +# ============================================================================== +# PIPELINE: CI — PR Commenter +# ------------------------------------------------------------------------------ +# Triggered by: Completion of the "CI — Development" workflow run. # -# Triggers: -# workflow_run → CI — Development (completed) +# Part of the Unified Rustarium CI/CD Workflow: +# [ ] 1. DEVELOPMENT (PR validation, preview docs) +# [ ] 2. MAIN (Post-merge validation, main branch docs) +# [ ] 3. NIGHTLY (Daily check, pre-release publish, nightly docs) +# [ ] 4. WEEKLY (Weekly regression checks) +# [ ] 5. RELEASE (Production PyPI & OCI publish, versioned docs) +# [ ] 6. UTILITY: CLEANUP (Daily stale docs/OCI image cleanup) +# -> [x] 7. UTILITY: PR COMMENTER (Coverage & builds reporting on PRs) <--- CURRENT WORKFLOW # -# Resolves the GitHub Actions fork permission issue. Workflows triggered -# via `pull_request` from a fork do not have `write` permissions to post -# comments. `workflow_run` executes in the context of the base repository -# and can safely download artifacts, compile reports, and comment on the PR. -# ============================================================================= +# High-Level Execution flow & Job Dependencies: +# +# [post_comment] (Extracts test reports & builds from caller run, posts comment on PR) +# +# ============================================================================== name: "PR Commenter" on: workflow_run: @@ -40,123 +48,96 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.event == 'pull_request' steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Resolve PR & SHA + id: context + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + github-token: ${{ github.token }} + script: | + const run = context.payload.workflow_run; + const pr = (run.pull_requests[0] || (await github.rest.search.issuesAndPullRequests({ + q: `is:pr repo:${context.repo.owner}/${context.repo.repo} sha:${run.head_sha}` + })).data.items[0])?.number; + if (!pr) core.setFailed("No PR found"); + core.setOutput("number", pr); + core.setOutput("sha_short", run.head_sha.substring(0, 7)); - name: Download all artifacts - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372ec801d16 # v4.1.8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: "Compile and Post PR Status Comments" - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + github-token: ${{ github.token }} + continue-on-error: true + - name: Compile Coverage Report + id: coverage + run: | + python3 -c ' + import os, pathlib + workspace = pathlib.Path(".") + summary_rows = [] + details = [] + for fp in workspace.rglob("*coverage_tests-*.md"): + content = fp.read_text() + parts = fp.parts + cov_part = next((p for p in parts if p.startswith("coverage-")), "") + if "rust" in cov_part: + py_ver = "Rust" + suite = "Func" + else: + py_ver = cov_part.split("-")[2] if len(cov_part.split("-")) > 2 else "Python" + suite = next((s for s in ["func", "unit", "int", "e2e"] if s in fp.name), "Unknown").capitalize() + total_line = next((l for l in content.splitlines() if "TOTAL" in l.upper()), None) + if total_line: + cells = [c.strip() for c in total_line.replace("*", "").split("|") if c.strip()] + summary_rows.append(f"| {"🦀" if py_ver == "Rust" else "🐍"} {suite} | {py_ver} | {cells[1]} | {cells[2]} | **{cells[3]}** |") + details.append(f"
\n🔍 {suite} Suite ({py_ver})\n\n{content}\n
") + if summary_rows: + body = "### 📊 Test Coverage Results\n\n| Test Suite | Python/Language | Stmts/Lines | Miss/Branches | Coverage/Functions |\n| :--- | :---: | :---: | :---: | :---: |\n" + "\n".join(sorted(summary_rows)) + "\n\n
\n\n### 📄 Detailed Reports\n\n" + "\n".join(details) + with open(os.environ["GITHUB_OUTPUT"], "a") as f: f.write(f"coverage_body={body}\n") + ' + - name: Compile Build Report + id: builds + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: - script: |- - const fs = require('fs'); - const path = require('path'); - const workflowRun = context.payload.workflow_run; - // 1. Get the pull request associated with the commit - const response = await github.rest.search.issuesAndPullRequests({ - q: `is:pr repo:${context.repo.owner}/${context.repo.repo} sha:${workflowRun.head_sha}` - }); - if (response.data.total_count === 0) { - console.log('No PR found for this commit.'); - return; - } - const prNumber = response.data.items[0].number; - // 2. Scan workspace for downloaded artifacts - const workspace = process.env.GITHUB_WORKSPACE || '.'; - const dirs = fs.readdirSync(workspace); - let docsComment = ""; - const buildComments = []; - let pythonCoverageReports = ""; - let rustCoverageReports = ""; - let hasCoverage = false; - for (const dirName of dirs) { - const dirPath = path.join(workspace, dirName); - if (!fs.statSync(dirPath).isDirectory()) continue; - // Parse docs comment - if (dirName.startsWith('pr-comment-docs-')) { - const filePath = path.join(dirPath, 'docs-comment.txt'); - if (fs.existsSync(filePath)) { - docsComment = fs.readFileSync(filePath, 'utf8'); - } - } - // Parse build comment - if (dirName.startsWith('pr-comment-build-py')) { - const filePath = path.join(dirPath, 'build-comment.txt'); - if (fs.existsSync(filePath)) { - buildComments.push(fs.readFileSync(filePath, 'utf8')); - } - } - // Parse python coverage - if (dirName.startsWith('coverage-report-unit-python-py')) { - const pyVerMatch = dirName.match(/python-(py\d+\.\d+)/); - const pyVer = pyVerMatch ? pyVerMatch[1] : "Python"; - const funcCovPath = path.join(dirPath, 'coverage_tests-func.md'); - if (fs.existsSync(funcCovPath)) { - const content = fs.readFileSync(funcCovPath, 'utf8'); - pythonCoverageReports += `#### 🐍 Python Coverage (${pyVer} — Functional Tests)\n\n${content}\n\n`; - hasCoverage = true; - } - const e2eCovPath = path.join(dirPath, 'coverage_tests-e2e.md'); - if (fs.existsSync(e2eCovPath)) { - const content = fs.readFileSync(e2eCovPath, 'utf8'); - pythonCoverageReports += `#### 🐍 Python Coverage (${pyVer} — E2E Tests)\n\n${content}\n\n`; - hasCoverage = true; - } - } - // Parse rust coverage - if (dirName.startsWith('coverage-report-unit-rust-')) { - const funcCovPath = path.join(dirPath, 'coverage_tests-func.md'); - if (fs.existsSync(funcCovPath)) { - const content = fs.readFileSync(funcCovPath, 'utf8'); - rustCoverageReports += `#### 🦀 Rust Coverage (Functional Tests)\n\n${content}\n\n`; - hasCoverage = true; - } - } - } - // 3. Compile and Post Comments - // Separate Comment: Docs Preview (if built) - if (docsComment) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: docsComment - }); - console.log("Posted Documentation Preview comment."); - } - // Separate Comments: Build Artifacts (if created) - for (const buildComment of buildComments) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: buildComment + script: | + const artifacts = (await github.rest.actions.listWorkflowRunArtifacts({ ...context.repo, run_id: ${{ github.event.workflow_run.id }} })).data.artifacts; + const target = artifacts.filter(a => a.name.startsWith('build-artifact-')); + if (target.length > 0) { + let body = `### 📦 Build Artifacts\n\nFor commit \`${{ steps.context.outputs.sha_short }}\`:\n`; + target.forEach(a => { + body += `- 📦 [${a.name}](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${{ github.event.workflow_run.id }}/artifacts/${a.id})\n`; }); - console.log("Posted Package Build comment."); - } - // Separate Comment: Status and Test Coverage compilation - const status = workflowRun.conclusion; - let statusBody = `## CI Development Pipeline Status\n\n`; - if (status === 'success') { - statusBody += `✅ **Pipeline**: Completed successfully. [View Run Details](${workflowRun.html_url})\n\n`; - } else { - statusBody += `❌ **Pipeline**: Failed or was cancelled. [Check logs](${workflowRun.html_url})\n\n`; + core.setOutput("body", body); } - if (hasCoverage) { - statusBody += `### 📊 Compiled Code Coverage Results\n\n`; - if (pythonCoverageReports) { - statusBody += `### Python Test Suites\n\n${pythonCoverageReports}`; - } - if (rustCoverageReports) { - statusBody += `### Rust Test Suites\n\n${rustCoverageReports}`; - } - } else { - statusBody += `No code coverage reports were compiled for this run.`; + - name: Compile Docs Preview + id: docs + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const jobs = (await github.rest.actions.listJobsForWorkflowRun({ ...context.repo, run_id: ${{ github.event.workflow_run.id }} })).data.jobs; + if (jobs.find(j => j.name === "Build & Deploy Docs" && j.conclusion === "success")) { + const url = `https://${context.repo.owner.toLowerCase()}.github.io/${context.repo.repo}/review/pr-${{ steps.context.outputs.number }}/`; + core.setOutput("body", `### 📖 Documentation\n\n🔗 [PR Documentation Preview](${url})`); } - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: statusBody - }); - console.log("Posted Pipeline Status and Coverage comment."); + - name: Upsert Coverage Comment + if: steps.coverage.outputs.coverage_body + uses: ./.github/actions/utility/comment-upsert + with: + pr-number: ${{ steps.context.outputs.number }} + marker: "" + body: ${{ steps.coverage.outputs.coverage_body }} + - name: Upsert Build Comment + if: steps.builds.outputs.body + uses: ./.github/actions/utility/comment-upsert + with: + pr-number: ${{ steps.context.outputs.number }} + marker: "" + body: ${{ steps.builds.outputs.body }} + - name: Upsert Docs Comment + if: steps.docs.outputs.body + uses: ./.github/actions/utility/comment-upsert + with: + pr-number: ${{ steps.context.outputs.number }} + marker: "" + body: ${{ steps.docs.outputs.body }} diff --git a/.gitignore b/.gitignore index 25422d6..6682b35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # ============================================================================== # Operating Systems # ============================================================================== @@ -399,5 +413,11 @@ cdk.out/ # Generated reference documentation /.docs/ +/docs/reference/python_api/ + +# Generated version.py files +version.py +# Reference backup folder +.github-old/ diff --git a/AGENTS.md b/AGENTS.md index 8e01f40..56241b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,61 +1,132 @@ + + # AGENTS.md — AI Agent & Coding Assistant Guide -> **This file provides repository-specific instructions to AI coding agents** (e.g., OpenAI Codex, GitHub Copilot, Gemini, Claude, Cursor). -> Human contributors should refer to [DEVELOPING.md](DEVELOPING.md). +This file provides repository-specific context, setup instructions, executable commands, and security boundaries for AI coding assistants. + +## System Overview + +`rustarium` is a high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust. + +- **Primary Languages:** Python 3.10+ / Rust +- **Configuration & Build Backend:** Hatch (with Maturin backend for native Rust extension compilation) +- **Key Dependencies:** PyO3, Loguru, Pydantic / Pydantic-Settings v2, Typer, Opentelemetry + +## Core Directories & Architecture + +- `src/rustarium/`: Python interface and orchestrator client. + - `__main__.py`: CLI entrypoint (subcommands: `diagnose`, `setup`). + - `settings.py`: Pydantic settings schema for project options. + - `client.py`: Process client wrapper. + - `logging.py`: Loguru logging and telemetry hooks. +- `crates/rustarium-core/`: Core Rust library compiling PyO3 bindings (`rustarium._rust`). + - `src/lib.rs`: Rust module bindings and PyO3 setup. + - `src/sum.rs`: Sandboxed computation functions. +- `tests/`: Organized into `python/unit/` (isolated logic), `python/integration/` (subsystem interactions), and `e2e/` (orchestrator black-box integration). +- `docs/`: MkDocs Material documentation source using Zensical. +- `.github/workflows/`: CI/CD workflows. + +## Environment & Developer Workflows + +This project is configured to run using Hatch environments. Use the local `.venv` for all executions as instructed by the user. + +### 1. Setup & Bootstrapping + +Activate the environment and initialize Hatch: + +```bash +# Set up/update dependencies via Hatch inside virtualenv wrapper +.venv/bin/hatch env create +``` + +### 2. Testing Pipeline + +Tests are tiered across languages. Run targeted tests or full suite: + +```bash +# Run all Python functional tests (unit + integration) +.venv/bin/hatch run python:tests-func + +# Run Python unit tests only +.venv/bin/hatch run python:tests-unit + +# Run Python integration tests only +.venv/bin/hatch run python:tests-int + +# Run Rust unit and integration tests +.venv/bin/hatch run rust:tests + +# Run OCI Container Structure Tests (CST) +.venv/bin/hatch run oci:tests + +# Run E2E tests (builds dist wheel and installs it first) +.venv/bin/hatch run project:tests-e2e + +# Run all tests with coverage reports +.venv/bin/hatch run tests-cov +``` -## Project Context +### 3. Code Quality, Formatting & Types -**`rustarium`** A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust. -**Primary language:** `Python 3.10+`\ -**Package manager:** `Hatch` +Run formatting and quality gates before committing: -## Critical Constraints +```bash +# Auto-format Python, Rust, and project configuration files +.venv/bin/hatch run python:format +.venv/bin/hatch run rust:format +.venv/bin/hatch run project:format -> [!CAUTION] -> 1. **Never commit secrets.** Do not add API keys, tokens, or credentials anywhere. -> 1. **Do not modify `LICENSE` or `NOTICE`.** These are legally binding. -> 1. **Do not modify workflow trigger conditions** without human review. -> 1. **All new source files must include the Apache 2.0 copyright header.** -> 1. **Use `{{double_braces}}` for template placeholders.** Never hard-code them. +# Run lint checks (Ruff, Clippy, mdformat, yamlfix, taplo) +.venv/bin/hatch run python:lint +.venv/bin/hatch run rust:lint +.venv/bin/hatch run project:lint -## Repository Layout +# Run static type checks (Mypy via Ty for Python, cargo check for Rust) +.venv/bin/hatch run python:types +.venv/bin/hatch run rust:types +``` -- `.github/workflows/`: CI/CD pipelines. Files prefixed with `_` are reusable templates. -- `docs/`: Zensical documentation source. -- `src/`: Primary application source code. -- `tests/`: Organized into `python/unit/`, `python/integration/`, and `e2e/`. +### 4. Documentation & Packaging -## Executable Commands +```bash +# Build and serve docs locally (http://127.0.0.1:8000) +.venv/bin/hatch run project:docs-serve -- **Formatting:** `hatch run all:format` (or `hatch run python:format`, `hatch run rust:format`, `hatch run project:format`, `hatch run oci:format`) -- **Linting:** `hatch run all:lint` (cascades to all environments) or individual checks (e.g., `hatch run python:lint`, `hatch run project:lint`) -- **Type Checking:** `hatch run all:types` (cascades to python/rust check targets) or environment-specific -- **Testing:** `hatch run all:tests` (runs python + rust unit/integration and project e2e), `hatch run python:tests` (with optional `-cov` suffix) -- **Security Audits:** `hatch run all:security` or environment-specific (e.g., `hatch run python:security`) -- **Docs:** `hatch run project:docs-serve` (local dev server) / `hatch run project:docs` (static build using Zensical) -- **Build:** `hatch build` (Maturin Python package build) or `hatch run all:build` (builds python, OCI, docs) +# Build package distributions (sdist and wheel compiling Rust bindings) +.venv/bin/hatch build +``` -## Code Style & Patterns +## Security & Behavior Boundaries -- **No magic strings or numbers** — define constants. -- **Prefer explicit over implicit.** -- **One responsibility per module.** -- Every public function, class, and module must have a docstring. -- Follow [Conventional Commits](https://www.conventionalcommits.org/). +To maintain project integrity and security, agents must strictly adhere to the following rules: -## GitHub Actions Workflows +### 1. Secrets & Credentials -- **Reusable Templates (`_*.yml`):** Never trigger directly. Ensure changes are backward-compatible. -- **Lifecycle:** - - `development.yml`: PR open/sync (unit + smoke) - - `main.yml`: Push to `main` (unit + integration + sanity) - - `nightly.yml`, `weekly.yml`, `release.yml`: Standard scheduled/release flows. +- **Never commit secrets:** Never add API keys, tokens, or credentials anywhere. +- Run security audits using: `.venv/bin/hatch run project:security`. -## Documentation +### 2. Critical Files & CI Guardrails -- **`docs/`** and **`zensical.toml`** control the site. -- Use `{{placeholder}}` variables for templated fields (e.g., `rustarium`, `markurtz`). +- **Do not modify `LICENSE` or `NOTICE`.** +- **Do not modify GitHub Actions workflow triggers or steps** (in `.github/`) without explicit human review. +- **Apache 2.0 copyright header:** Every new source file (Python or Rust) must begin with the standard Apache 2.0 copyright and license notice. -## Agent Notes +### 3. Execution Constraints -_Add notes here when updating instructions for AI agents._ +- Always use tools installed in the `.venv` (e.g. `.venv/bin/hatch`, `.venv/bin/pytest`). +- Avoid global packages or running unverified external binaries. +- Do not add new external dependencies to `pyproject.toml` without verifying compatibility with Python 3.10+. diff --git a/CITATION.cff b/CITATION.cff index 313ecc9..0ba562b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,21 +1,27 @@ -# This CITATION.cff file was generated from a template. -# Update the placeholders below with your project's specific details. -# For more information on the CFF format, see: https://citation-file-format.github.io/ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. cff-version: 1.2.0 title: "rustarium" message: "If you use this software, please cite it as below." type: software -date-released: 2026-01-01 license: Apache-2.0 # You can list individual authors or an organization. # This defaults to the organization to match the README's BibTeX. authors: - - name: "markurtz" -# - family-names: "{{author_family_name}}" -# given-names: "{{author_given_name}}" -# orcid: "https://orcid.org/{{orcid}}" + - name: "Mark Kurtz" repository-code: "https://github.com/markurtz/rustarium" url: "https://markurtz.github.io/rustarium" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..321b8d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,17 @@ + + +Refer to [AGENTS.md](AGENTS.md) for build, testing, quality controls, and agent instructions. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 13a0af4..1201c98 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,19 @@ + + # Code of Conduct for `rustarium` ## Our Pledge @@ -61,8 +77,7 @@ representative at an online or offline event. > [!IMPORTANT] > Instances of abusive, harassing, or otherwise unacceptable behavior may be > reported to the community leaders responsible for enforcement by contacting -> -> +> one of the project maintainers. All complaints will be reviewed and investigated promptly and fairly. @@ -96,6 +111,6 @@ at [https://www.contributor-covenant.org/translations][translations]. [faq]: https://www.contributor-covenant.org/faq [homepage]: https://www.contributor-covenant.org -[mozilla coc]: https://github.com/mozilla/diversity +[mozilla coc]: https://www.mozilla.org/about/governance/policies/participation/ [translations]: https://www.contributor-covenant.org/translations [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb431aa..f1ab090 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,19 @@ + + # Contributing to rustarium First off, thank you for considering contributing to `rustarium`! It's people like you that make this project great. @@ -46,7 +62,7 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ### 3. Making Changes 1. **Fork the Repository:** Fork the `rustarium` repository to your GitHub account. -1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/add-new-feature`). +1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/wasm-sandbox-support`). 1. **Write Code:** Implement your changes, adhering to the project's coding standards. 1. **Write Tests:** Add unit tests or integration tests for your changes to ensure stability. 1. **Run Tests:** Ensure all tests and linters pass locally before committing. @@ -54,14 +70,13 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ### 4. Committing Your Changes - Write clear, concise commit messages. -- We recommend using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: add support for X`, `fix: resolve issue with Y`). -- If you are adding a new file, please include the appropriate Apache 2.0 copyright and license header at the top. +- We recommend using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: add wasm sandbox support`, `fix: resolve memory leak in orchestrator`). ### 5. Submitting a Pull Request -1. **Push your branch:** `git push origin your-branch-name`. +1. **Push your branch:** `git push origin feat/wasm-sandbox-support`. 1. **Open a Pull Request:** Open a PR against the `main` branch of the upstream repository. -1. **Fill out the PR Template:** Provide a clear description of your changes, link to any relevant issues (e.g., `Closes #123`), and complete any required checklists. +1. **Fill out the PR Template:** Provide a clear description of your changes, link to any relevant issues (e.g., `Closes #42`), and complete any required checklists. 1. **Pass CI:** Ensure all GitHub Actions CI checks pass. 1. **Review:** Address any feedback from the maintainers. Once approved and checks pass, a maintainer will merge your PR. diff --git a/Cargo.lock b/Cargo.lock index 069d72e..c46a182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,7 +240,7 @@ dependencies = [ [[package]] name = "rustarium-core" -version = "0.0.1" +version = "0.0.1-dev3+68da829" dependencies = [ "bincode", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 9bbce53..cb46ce9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,22 +17,35 @@ resolver = "2" members = ["crates/*"] [workspace.package] -version = "0.0.1" +version = "0.0.1-dev3+68da829" edition = "2021" authors = ["Mark Kurtz"] license = "Apache-2.0" repository = "https://github.com/markurtz/rustarium" [workspace.dependencies] -pyo3 = { version = "0.24.1", features = ["abi3-py310"] } -tokio = { version = "1.38", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" bincode = "1.3" -[workspace.metadata.bin] -cargo-audit = { version = "0.22.1", locked = true } -cargo-deny = { version = "0.19.6", locked = true } +[workspace.dependencies.pyo3] +version = "0.24.1" +features = ["abi3-py310"] + +[workspace.dependencies.tokio] +version = "1.38" +features = ["full"] + +[workspace.dependencies.serde] +version = "1.0" +features = ["derive"] + +[workspace.metadata.bin.cargo-audit] +version = "0.22.1" +locked = true + +[workspace.metadata.bin.cargo-deny] +version = "0.19.6" +locked = true [profile.dev] split-debuginfo = "unpacked" diff --git a/DEVELOPING.md b/DEVELOPING.md index 56ca1ad..58a683d 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -1,3 +1,19 @@ + + # Developing `rustarium` This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards. @@ -13,7 +29,7 @@ Ensure your system meets the requirements below to establish a consistent local ### Development Environment Container (.devcontainer) -- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **Dev Containers** extension installed. +- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **[Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers)** extension installed. - **Usage**: 1. Clone this repository: `git clone https://github.com/markurtz/rustarium.git` 1. Open the project folder in VS Code. @@ -22,7 +38,7 @@ Ensure your system meets the requirements below to establish a consistent local > [!NOTE] > **Local `.venv` vs. Hatch Environments**: -> The `uv sync` command creates a local `.venv` in the project root solely to provide VS Code extensions (like Pylance and Ruff) with a standard environment for editor autocomplete, hover information, and in-editor diagnostics. All command-line and automated task execution (formatting, linting, testing, building) is managed via **Hatch** isolated environments (`hatch run ...`). Do not activate or modify this root `.venv` directly for running tasks. +> The `uv sync` command creates a local `.venv` in the project root solely to provide VS Code extensions (like [Pylance](https://github.com/microsoft/pylance-release) and [Ruff](https://astral.sh/ruff)) with a standard environment for editor autocomplete, hover information, and in-editor diagnostics. All command-line and automated task execution (formatting, linting, testing, building) is managed via **[Hatch](https://hatch.pypa.io/)** isolated environments (`hatch run ...`). Do not activate or modify this root `.venv` directly for running tasks. ### Local Setup @@ -47,7 +63,7 @@ Ensure your system meets the requirements below to establish a consistent local > [!TIP] > **Editor Autocomplete Setup (Local)**: -> For local development outside of the Dev Container, if you want your editor (VS Code, PyCharm, etc.) to resolve imports and provide autocomplete/diagnostics, run `uv sync --all-groups --all-extras` once to create the local `.venv`. +> For local development outside of the Dev Container, if you want your editor (VS Code, [PyCharm](https://www.jetbrains.com/pycharm/), etc.) to resolve imports and provide autocomplete/diagnostics, run `uv sync --all-groups --all-extras` once to create the local `.venv`. ## Developer Quickstart @@ -87,10 +103,10 @@ Our build, verification, and execution pipelines are partitioned into target-spe - **`default`**: The base environment template. It configures shared environment variables (such as target paths, directory structures, and script file paths) and installs the core dependency groups. - **`all`**: The orchestrator environment. It defines cascading workflows to run formatting, linting, typing, security scanning, testing, and documentation generation across all components sequentially or concurrently. -- **`python`**: Encompasses Python-specific verification tools including Ruff for linting/formatting, Ty for type-checking, Pytest for testing, and Typer for CLI documentation generation. +- **`python`**: Encompasses Python-specific verification tools including [Ruff](https://astral.sh/ruff) for linting/formatting, [Ty](https://github.com/astral-sh/ty) for type-checking, [Pytest](https://docs.pytest.org/) for testing, and [Typer](https://typer.tiangolo.com/) for CLI documentation generation. - **`rust`**: Handles compiling, testing (`cargo test`), linting (`clippy`), type checking (`cargo check`), and API documentation generation (`cargo doc`) for the underlying Rust extension modules. -- **`oci`**: Manages OCI container builds (`docker build`), compose verification (`docker compose config`), linting (`hadolint`), security auditing (`trivy`, `dockle`), and container structure tests (`cstest`). -- **`project`**: Targets repository-wide configuration and file standards, including Markdown formatting (`mdformat`), configuration checkouts (`yamlfix`, `yamllint`, `taplo`), security baselines (`detect-secrets`, `checkov`), link checkers, and site compilation (using the **Zensical** static site generator/documentation compiler). +- **`oci`**: Manages OCI container builds (`docker build`), compose verification (`docker compose config`), linting ([hadolint](https://github.com/hadolint/hadolint)), security auditing ([trivy](https://trivy.dev/), [dockle](https://github.com/goodwithtech/dockle)), and container structure tests ([cstest](https://github.com/GoogleContainerTools/container-structure-test)). +- **`project`**: Targets repository-wide configuration and file standards, including Markdown formatting (`[mdformat](https://github.com/executablebooks/mdformat)`), configuration checkouts (`[yamlfix](https://github.com/lyz-code/yamlfix)`, `[yamllint](https://github.com/adrienverge/yamllint)`, `[taplo](https://taplo.tamasfe.dev/)`), security baselines (`[detect-secrets](https://github.com/Yelp/detect-secrets)`, `[checkov](https://www.checkov.io/)`), link checkers, and site compilation (using the **[Zensical](https://zensical.org)** static site generator/documentation compiler). ## Coding Workflows @@ -131,27 +147,27 @@ This workflow enforces code quality, style conventions, static type correctness, #### Code Formatting - **Tools / Methodology / Rationale**: - - **Python**: Uses `ruff` to automatically check/fix imports and format code layout. This delivers high-performance style standardization. + - **Python**: Uses `[ruff](https://astral.sh/ruff)` to automatically check/fix imports and format code layout. This delivers high-performance style standardization. - **Rust**: Uses `cargo fmt` to enforce the official Rust layout style and `cargo clippy --fix` to safely resolve compiler styling suggestions. - - **OCI**: Uses `dclint` (via a helper script) to auto-format Docker Compose files. While `dclint` is primarily a compose linter, the format step (`hatch run oci:format`) executes it with the `--fix` flag to automatically correct lint errors and standard style issues in place. (Dockerfile linting/validation is handled separately by `hadolint`). - - **Project**: Employs `mdformat` for Markdown, `yamlfix` for YAML files, and `taplo` for TOML file formatting to maintain a uniform structure for all configuration and documentation files. + - **OCI**: Uses `[dclint](https://github.com/zavoloklom/docker-compose-linter)` (via a helper script) to auto-format Docker Compose files. While `dclint` is primarily a compose linter, the format step (`hatch run oci:format`) executes it with the `--fix` flag to automatically correct lint errors and standard style issues in place. (Dockerfile linting/validation is handled separately by `[hadolint](https://github.com/hadolint/hadolint)`). + - **Project**: Employs `[mdformat](https://github.com/executablebooks/mdformat)` for Markdown, `[yamlfix](https://github.com/lyz-code/yamlfix)` for YAML files, and `[taplo](https://taplo.tamasfe.dev/)` for TOML file formatting to maintain a uniform structure for all configuration and documentation files. - **Expected Outputs & Locations**: - In-place modifications applied directly to the files targeted by the respective environment variables: `PYTHON_TARGETS`, `RUST_MANIFEST`, `MDFORMAT_TARGETS` (Markdown targets), `YAML_TARGETS`, and `TOML_TARGETS`. #### Linting & Verification - **Tools / Methodology / Rationale**: - - **Python**: Runs `ruff check` and `ruff format --check` to verify compliance with PEP 8 and project style guidelines without modifying files. + - **Python**: Runs `[ruff](https://astral.sh/ruff) check` and `[ruff](https://astral.sh/ruff) format --check` to verify compliance with PEP 8 and project style guidelines without modifying files. - **Rust**: Executes `cargo clippy` and `cargo fmt --check` to run comprehensive static analysis lints, treating all warnings as errors (`-D warnings`). - - **OCI**: Uses `hadolint` to validate Dockerfile syntax and standard practices, and runs `docker compose config` to verify the syntactic and semantic validity of compose files. - - **Project**: Runs `mdformat --check` to check Markdown formatting, `yamlfix --check` and `yamllint` for YAML files, and `taplo check` for TOML configuration syntax. + - **OCI**: Uses `[hadolint](https://github.com/hadolint/hadolint)` to validate Dockerfile syntax and standard practices, and runs `docker compose config` to verify the syntactic and semantic validity of compose files. + - **Project**: Runs `[mdformat](https://github.com/executablebooks/mdformat) --check` to check Markdown formatting, `[yamlfix](https://github.com/lyz-code/yamlfix) --check` and `[yamllint](https://github.com/adrienverge/yamllint)` for YAML files, and `[taplo](https://taplo.tamasfe.dev/) check` for TOML configuration syntax. - **Expected Outputs & Locations**: - Summary reports, warnings, and errors output directly to the terminal stdout/stderr. Standard exit codes (non-zero on failures) are used to gate CI pipelines. #### Static Type Checking - **Tools / Methodology / Rationale**: - - **Python**: Employs Astral's `ty check` frontend to statically analyze and verify Python type annotations. + - **Python**: Employs Astral's `[ty check](https://github.com/astral-sh/ty)` frontend to statically analyze and verify Python type annotations. - **Rust**: Executes `cargo check --all-targets` to quickly analyze the Rust codebase and verify compile-time type safety without generating binary artifacts. - **Expected Outputs & Locations**: - Type checker error listings and tracebacks are printed to the terminal console. @@ -159,10 +175,10 @@ This workflow enforces code quality, style conventions, static type correctness, #### Security & Vulnerability Auditing - **Tools / Methodology / Rationale**: - - **Python**: Employs `semgrep` for semantic pattern matching, `pip-audit` to detect known vulnerabilities in Python packages, and `ruff check --select S` to check for security vulnerabilities. + - **Python**: Employs `[semgrep](https://semgrep.dev/)` for semantic pattern matching, `[pip-audit](https://github.com/pypa/pip-audit)` to detect known vulnerabilities in Python packages, and `[ruff](https://astral.sh/ruff) check --select S` to check for security vulnerabilities. - **Rust**: Uses `cargo-run-bin` (`cargo bin cargo-audit` and `cargo bin cargo-deny`) to scan dependencies for CVEs reported in the Rust Sec Advisory Database, and to audit licenses and sources. The required versions of these tools are locked in the root workspace `Cargo.toml` and cached locally in `.bin/`. - - **OCI**: Scans built containers using `dockle` (verifies image best practices/secrets) and `trivy` (scans OS-level packages for CVEs). - - **Project**: Employs `detect-secrets` to scan for accidentally committed secrets against a baseline, and `checkov` to scan infrastructure-as-code files and development configurations. + - **OCI**: Scans built containers using `[dockle](https://github.com/goodwithtech/dockle)` (verifies image best practices/secrets) and `[trivy](https://trivy.dev/)` (scans OS-level packages for CVEs). + - **Project**: Employs `[detect-secrets](https://github.com/Yelp/detect-secrets)` to scan for accidentally committed secrets against a baseline, and `[checkov](https://www.checkov.io/)` to scan infrastructure-as-code files and development configurations. - **Expected Outputs & Locations**: - Standard reports output to the console. - Project environment updates and validates the secrets baseline file located at `.detect-secrets.scan.json`. Run `hatch run project:security-update` to update this baseline file. @@ -178,20 +194,22 @@ Code coverage runs collect data during test executions and format them into huma - **Python Coverage**: Configured to output to `coverage/python/` (`PYTHON_COV_DIR`). The test suites automatically output terminal reports and compile Markdown reports (e.g., `coverage_tests-unit.md`). - **Rust Coverage**: Configured to output to `coverage/rust/` (`RUST_COV_DIR`). It utilizes `cargo llvm-cov` to collect coverage, outputs `.json` metrics, and validates them against coverage gates via `covgate`, exporting Markdown reports (e.g., `coverage_tests-unit.md`). -| Test Suite | Python Command | Rust Command | OCI Command | Project Command | All Command | -| :------------------------ | :-------------------------------- | :------------------------------ | :---------------------------- | :-------------------------------- | :----------------------------- | -| **All Local Tests** | N/A | N/A | N/A | N/A | `hatch run all:tests` | -| **All Tests + Coverage** | N/A | N/A | N/A | N/A | `hatch run all:tests-cov` | -| **Functional Tests** | `hatch run python:tests-func` | `hatch run rust:tests-func` | N/A | N/A | `hatch run all:tests-func` | -| **Func Tests + Coverage** | `hatch run python:tests-func-cov` | `hatch run rust:tests-func-cov` | N/A | N/A | `hatch run all:tests-func-cov` | -| **Unit Tests** | `hatch run python:tests-unit` | `hatch run rust:tests-unit` | N/A | N/A | `hatch run all:tests-unit` | -| **Unit Tests + Coverage** | `hatch run python:tests-unit-cov` | `hatch run rust:tests-unit-cov` | N/A | N/A | `hatch run all:tests-unit-cov` | -| **Integration Tests** | `hatch run python:tests-int` | `hatch run rust:tests-int` | N/A | `hatch run project:tests-int` | `hatch run all:tests-int` | -| **Int Tests + Coverage** | `hatch run python:tests-int-cov` | `hatch run rust:tests-int-cov` | N/A | `hatch run project:tests-int-cov` | `hatch run all:tests-int-cov` | -| **End-to-End Tests** | `hatch run python:tests-e2e` | N/A | `hatch run oci:tests-e2e` | `hatch run project:tests-e2e` | `hatch run all:tests-e2e` | -| **E2E Tests + Coverage** | `hatch run python:tests-e2e-cov` | N/A | `hatch run oci:tests-e2e-cov` | `hatch run project:tests-e2e-cov` | `hatch run all:tests-e2e-cov` | - -*\* Note: While Hatch commands for `N/A` cells can technically be run (and will print a message stating that the test suite is not defined for that environment), they have no logical test targets or execution paths. They are marked `N/A` for clarity.*| +| Test Suite | Python Command | Rust Command | OCI Command | Project Command | All Command | +| :------------------------- | :-------------------------------- | :------------------------------ | :---------------------------- | :---------------------------------- | :----------------------------- | +| **All Local Tests** | N/A | N/A | N/A | N/A | `hatch run all:tests` | +| **All Tests + Coverage** | N/A | N/A | N/A | N/A | `hatch run all:tests-cov` | +| **Functional Tests** | `hatch run python:tests-func` | `hatch run rust:tests-func` | N/A | N/A | `hatch run all:tests-func` | +| **Func Tests + Coverage** | `hatch run python:tests-func-cov` | `hatch run rust:tests-func-cov` | N/A | N/A | `hatch run all:tests-func-cov` | +| **Unit Tests** | `hatch run python:tests-unit` | `hatch run rust:tests-unit` | N/A | N/A | `hatch run all:tests-unit` | +| **Unit Tests + Coverage** | `hatch run python:tests-unit-cov` | `hatch run rust:tests-unit-cov` | N/A | N/A | `hatch run all:tests-unit-cov` | +| **Integration Tests** | `hatch run python:tests-int` | `hatch run rust:tests-int` | N/A | `hatch run project:tests-int` | `hatch run all:tests-int` | +| **Int Tests + Coverage** | `hatch run python:tests-int-cov` | `hatch run rust:tests-int-cov` | N/A | `hatch run project:tests-int-cov` | `hatch run all:tests-int-cov` | +| **End-to-End Tests** | `hatch run python:tests-e2e` | N/A | `hatch run oci:tests-e2e` | `hatch run project:tests-e2e` | `hatch run all:tests-e2e` | +| **E2E Tests + Coverage** | `hatch run python:tests-e2e-cov` | N/A | `hatch run oci:tests-e2e-cov` | `hatch run project:tests-e2e-cov` | `hatch run all:tests-e2e-cov` | +| **Link Checks** | N/A | N/A | N/A | `hatch run project:link-checks` | N/A | +| **Link Checks + Coverage** | N/A | N/A | N/A | `hatch run project:link-checks-cov` | N/A | + +*\* Note: While Hatch commands for `N/A` cells can technically be run (and will print a message stating that the test suite is not defined for that environment), they have no logical test targets or execution paths. They are marked `N/A` for clarity.* #### Test Suites Breakdown @@ -232,34 +250,82 @@ Code coverage runs collect data during test executions and format them into huma - **Methodology & Rationale**: - **Python**: Compiles Python packages with `hatch build`, force reinstalls them via `pip`, and runs pytest against `tests/e2e` (`E2E_TESTS`) to verify CLI commands and package distribution paths in a black-box environment. - **OCI**: Builds the OCI image and executes Google's Container Structure Tests (`cstest` via `scripts/run_oci.py`) to confirm that the image metadata, file layouts, and execution endpoints conform to specifications. - - **Project**: Executes `scripts/check_links.py` to recursively crawl project documents (`MDFORMAT_TARGETS`) and verify all internal/external links resolve successfully. + - **Project**: Runs automated tests across the `examples/` directory using pytest to verify real-world integrations. - **Expected Outputs & Locations**: - **Python**: Outputs `coverage/python/coverage_tests-e2e.md`. - **OCI**: Outputs Container Structure Test results to the console. + - **Project**: Outputs example test execution summaries to the console. + +#### Link Checking (`link-checks` / `link-checks-cov`) + +- **Methodology & Rationale**: + - **Project**: Executes `scripts/check_links.py` to recursively crawl project documents (`MDFORMAT_TARGETS`) and verify all internal/external links resolve successfully. +- **Expected Outputs & Locations**: - **Project**: Outputs link-checking validation summaries to the console. -#### Test Categorization & Custom Markers +#### Test Categorization & Test Pathways + +To manage test execution speed and pipeline efficiency, every Python test is categorized into one of our three test pathways: **smoke**, **sanity**, or **regression**. These pathways directly govern how frequently and in which environments those tests are executed in CI/CD pipelines. -To manage test execution speed and pipeline efficiency, every Python test must be decorated with one of our three custom pytest markers. These markers directly govern how frequently and in which environments those tests are executed in CI/CD pipelines. +##### Pathway Specification & Filtering -##### Test Categories +A test's pathway can be specified and detected in one of two ways: -- **`@pytest.mark.smoke`**: +1. **By Marker**: Decorating the test function or class with a custom pytest marker (e.g., `@pytest.mark.smoke`, `@pytest.mark.sanity`, `@pytest.mark.regression`). +1. **By Name**: Including the pathway name in the test function or class name (e.g., `def test_smoke_initialization()`, `class TestSanityCore`, `def test_regression_bug_fix()`). + +##### Test Pathways Breakdown + +- **`smoke`**: - **Encapsulation & Scope**: Extremely fast, non-flaky, critical-path verification checks. These confirm that the fundamental, basic logic of the application functions correctly (e.g., orchestrator bootstrap, CLI command recognition). - **Execution Frequency**: Run on **every commit and Pull Request** (e.g., `development.yml`) as a quick health gate. -- **`@pytest.mark.sanity`**: +- **`sanity`**: - **Encapsulation & Scope**: Detailed, comprehensive tests of core system behaviors, APIs, and edge cases. These verify that the main business logic functions robustly but may take slightly longer than smoke tests. - **Execution Frequency**: Run on **pushes to the main branch** (e.g., `main.yml`) and release branches to ensure overall stability of the codebase. -- **`@pytest.mark.regression`**: +- **`regression`**: - **Encapsulation & Scope**: Deep, system-wide, and heavy integration/E2E regression verification checks. These ensure that complex interactions, edge cases, and past bugs do not reappear. - **Execution Frequency**: Run on **nightly, weekly, and release schedule pipelines** due to their longer execution time. -> [!IMPORTANT] -> **Mandatory Tagging Policy**: -> Every Python test MUST be decorated with at least one of these markers. Unmarked tests will not align with our CI scheduling patterns and may be skipped or cause checks to fail. -> -> **Pytest Registration Rule**: -> Any new custom markers must be explicitly registered under `[tool.pytest.ini_options]` in [pyproject.toml](./pyproject.toml). Unregistered markers will trigger warnings and fail CI/CD gating pipelines. +##### Default Pathway Execution + +If no specific filtering arguments, markers, or keyword flags are provided, pytest assumes a **regression** pathway by default. + +Running the test suite without any arguments executes a full regression run. This is because a default regression run executes: + +- All smoke tests +- All sanity tests +- All regression tests +- Any tests not marked or named under a specific category + +##### Filtering Python Tests + +Hatch dynamically passes CLI arguments through to the underlying `pytest` execution via the `{args}` placeholder configured in [pyproject.toml](./pyproject.toml). To filter by test pathways (`smoke`, `sanity`, or `regression`), use pytest's keyword option (`-k`). This correctly matches both annotated markers and naming patterns (e.g., pathway keywords in the function or class name). + +- **Run only smoke tests**: + ```bash + hatch run python:tests-unit -k smoke + ``` +- **Run sanity and smoke tests**: + ```bash + hatch run python:tests-unit -k "sanity or smoke" + ``` + +##### Testing a Specific Sub-Package or File + +Hatch environments make it easy to target a specific test directory, sub-package, or single file by appending the path to your `hatch run` command. The provided path will override the default directories configured in `pyproject.toml`. + +- **Run all tests in a specific file**: + ```bash + hatch run python:tests-unit tests/python/unit/test_settings.py + ``` +- **Run tests in a specific sub-package / directory**: + ```bash + hatch run python:tests-unit tests/python/unit/compat/ + ``` +- **Run functional tests for a specific integration file**: + ```bash + hatch run python:tests-func tests/python/integration/test_utils.py + ``` ##### Rust Test Categories @@ -279,38 +345,17 @@ Always ensure that your Rust tests are named with one of these substrings if the > 1. Use distinct, unambiguous suffixes or prefixes for tests (e.g. `_smoke`, `_sanity`, `_regression`) and avoid mixing these keywords. > 1. For larger test suites, isolate tests into separate integration test binaries under the `tests/` directory (e.g. `tests/smoke.rs`, `tests/sanity.rs`, `tests/regression.rs`) and run them directly (e.g. `cargo test --test smoke`) to achieve strict isolation. -##### Filtering Tests by Marker (Passing Arguments) - -Hatch dynamically passes CLI arguments through to the underlying `pytest` execution via the `{args}` placeholder configured in [pyproject.toml](./pyproject.toml). You can target specific markers using the `-m` flag. - -- **Run only smoke tests**: - ```bash - hatch run python:tests-unit -m smoke - ``` -- **Run sanity and smoke tests**: - ```bash - hatch run python:tests-unit -m "sanity or smoke" - ``` -- **Exclude regression tests**: - ```bash - hatch run python:tests-func -m "not regression" - ``` -- **Orchestrate across all environments using args**: - ```bash - hatch run all:tests-func -m smoke - ``` - ### Documentation Workflows -Our documentation is managed as code. It includes auto-generated CLI references, compiled Rust crates API docs, and a unified project site built using **Zensical**. +Our documentation is managed as code. It includes auto-generated CLI references, compiled Rust API reference pages, and a unified project site built using **[Zensical](https://zensical.org)**. > [!NOTE] > **Zensical Documentation Tool**: -> Zensical is a static site generator and documentation compiler configured via `zensical.toml` that integrates MkDocs and its plugin ecosystem (such as `mkdocstrings` and macros) under a simplified configuration structure. +> [Zensical](https://zensical.org) is a static site generator and documentation compiler configured via `zensical.toml` that integrates [MkDocs](https://www.mkdocs.org/) and its plugin ecosystem (such as [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) and [macros](https://mkdocs-macros-plugin.readthedocs.io/)) under a simplified configuration structure. #### CLI Documentation Generation -- **Tools / Methodology / Rationale**: Uses the `typer` utility to compile and output reference docs directly from the Python entrypoint `src/rustarium/__main__.py`. +- **Tools / Methodology / Rationale**: Uses the `[typer](https://typer.tiangolo.com/)` utility to compile and output reference docs directly from the Python entrypoint `src/rustarium/__main__.py`. - **Command**: `hatch run python:docs` - **Expected Outputs & Locations**: A generated Markdown reference file at `.docs/cli.md`. @@ -322,8 +367,8 @@ Our documentation is managed as code. It includes auto-generated CLI references, #### Project Website Compilation -- **Tools / Methodology / Rationale**: Compiles the final developer documentation site via **Zensical**, incorporating the general Markdown guides, Python CLI docs, and Rust API docs. -- **Command**: `hatch run project:docs` (or `hatch run all:docs` to generate Python/Rust docs and compile project docs together) +- **Tools / Methodology / Rationale**: Compiles the final developer documentation site via **Zensical**, incorporating the general Markdown guides, Python CLI docs, and Rust API reference documentation. +- **Command**: `hatch run project:docs` (or `hatch run all:docs` to generate Python and Rust docs and compile project docs together) - **Expected Outputs & Locations**: Static build files compiled to the `site/` directory. > [!TIP] @@ -356,7 +401,7 @@ These workflows handle compiling code, bundling extension modules, and building ## CI/CD Workflows -We maintain high quality gates using git workflows, automated reviews, and GitHub Actions pipelines. +We maintain high quality gates using git workflows, automated reviews, and [GitHub Actions](https://github.com/features/actions) pipelines. ### Version Control Standards @@ -381,7 +426,7 @@ We maintain high quality gates using git workflows, automated reviews, and GitHu Our pipelines use a highly modular and DRY architecture to avoid duplication of setup steps: -- **Tools**: GitHub Actions +- **Tools**: [GitHub Actions](https://github.com/features/actions) - **Configuration / Manifest Files**: Reusable actions under `.github/actions/...` and triggers under `.github/workflows/...` @@ -389,12 +434,12 @@ Our pipelines use a highly modular and DRY architecture to avoid duplication of - All composite actions (Python, Rust, OCI, and Project) support an optional `python-version` parameter. - If omitted, actions standardize on the oldest supported version (default: `"3.10"`). - - All composite actions using change detection (`dorny/paths-filter`) support a `force-run` parameter (default: `"false"`). When set to `"true"`, it bypasses path-filtering check gates and executes the steps unconditionally (used in scheduled and release workflows). + - All composite actions using change detection (`[dorny/paths-filter](https://github.com/dorny/paths-filter)`) support a `force-run` parameter (default: `"false"`). When set to `"true"`, it bypasses path-filtering check gates and executes the steps unconditionally (used in scheduled and release workflows). - **OCI Tools Native Execution**: - The repository utilizes unified platform-agnostic OCI runner logic (`scripts/run_oci.py`). - - When running in CI under `.github/actions/oci/`, the actions natively install OCI scanning and linting tools (`hadolint`, `dclint`, `dockle`, `trivy`, and `container-structure-test`) on the runner. + - When running in CI under `.github/actions/oci/`, the actions natively install OCI scanning and linting tools (`[hadolint](https://github.com/hadolint/hadolint)`, `[dclint](https://github.com/zavoloklom/docker-compose-linter)`, `[dockle](https://github.com/goodwithtech/dockle)`, `[trivy](https://trivy.dev/)`, and `[container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)`) on the runner. - This native pre-installation ensures that `run_oci.py` executes these binaries directly on the host machine, bypassing the performance overhead and Docker socket mounting requirements of containerized container-in-container execution. > [!WARNING] @@ -417,7 +462,7 @@ Our pipelines use a highly modular and DRY architecture to avoid duplication of - `security`: Runs dependency audits, secrets checks, and security linters. - `tests`: Runs unit, integration, and E2E tests, accepting `test-level`, `test-category`, and `generate-coverage` inputs. - `build`: Compiles wheels (Python), workspace crates (Rust), container images (OCI), or all elements (Project). - - `publish`: Publishes release packages to PyPI (Python) or container images to GHCR (OCI). + - `publish`: Publishes release packages to [PyPI](https://pypi.org/) (Python) or container images to [GHCR](https://github.com/features/packages) (OCI). - **Workflows (`.github/workflows/...`)**: Triggered pipelines separated into: @@ -433,13 +478,13 @@ Our pipelines use a highly modular and DRY architecture to avoid duplication of ### Local Workflow Testing with `act` -You can test and validate GitHub Actions workflows locally on your development machine using [`nektos/act`](https://github.com/nektos/act). This ensures that workflows run correctly before you push changes to GitHub. +You can test and validate [GitHub Actions](https://github.com/features/actions) workflows locally on your development machine using [nektos/act](https://github.com/nektos/act). This ensures that workflows run correctly before you push changes to GitHub. #### Prerequisites -1. Install **Docker** (required by `act` to spin up runner containers). +1. Install **[Docker](https://www.docker.com/)** (required by `act` to spin up runner containers). 1. Install `act` using your package manager: - - macOS (Homebrew): `brew install act` + - macOS ([Homebrew](https://brew.sh/)): `brew install act` - Linux (curl): `curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash` > [!IMPORTANT] @@ -451,25 +496,18 @@ You can test and validate GitHub Actions workflows locally on your development m Run `act` from the repository root: - **List all jobs**: - ```bash act -l ``` - - **Run the default (pull_request) event (runs Development Pipeline)**: - ```bash act pull_request ``` - - **Run a specific job (e.g., project-quality)**: - ```bash act -j project-quality ``` - - **Dry-run a workflow (displays steps without execution)**: - ```bash act -n ``` @@ -496,10 +534,10 @@ Because composite actions use `dorny/paths-filter` to detect path-level changes, ### Security & Code Scanning Gates -- **Tools**: `detect-secrets` (secret scanning), `checkov` (infrastructure auditing), `semgrep` (semantic scanning), `pip-audit` (Python package audits), `cargo-audit` / `cargo-deny` (Rust crate audits), and `trivy` / `dockle` (OCI image scanning). +- **Tools**: [detect-secrets](https://github.com/Yelp/detect-secrets) (secret scanning), [checkov](https://www.checkov.io/) (infrastructure auditing), [semgrep](https://semgrep.dev/) (semantic scanning), [pip-audit](https://github.com/pypa/pip-audit) (Python package audits), `cargo-audit` / `cargo-deny` (Rust crate audits), and [trivy](https://trivy.dev/) / [dockle](https://github.com/goodwithtech/dockle) (OCI image scanning). - **Workflow & Rationale**: - - **Secret Gating**: `detect-secrets` runs locally and in PR gates against the committed `.detect-secrets.scan.json` baseline to prevent credential leaks. - - **Static Analysis & CVE Auditing**: Semgrep, Trivy, Checkov, and the Cargo/pip audits run automatically as background checks on every pull request to guarantee compliance with our security baseline. + - **Secret Gating**: [detect-secrets](https://github.com/Yelp/detect-secrets) runs locally and in PR gates against the committed `.detect-secrets.scan.json` baseline to prevent credential leaks. + - **Static Analysis & CVE Auditing**: [Semgrep](https://semgrep.dev/), [Trivy](https://trivy.dev/), [Checkov](https://www.checkov.io/), [pip-audit](https://github.com/pypa/pip-audit), and the Rust Cargo audits run automatically as background checks on every pull request to guarantee compliance with our security baseline. ______________________________________________________________________ diff --git a/Dockerfile b/Dockerfile index 7973ef2..e9bb890 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,7 +42,7 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --de # hadolint ignore=DL3013 RUN pip install --no-cache-dir uv hatch maturin gitversioned -ARG VERSION=0.1.0 +ARG VERSION=0.0.1.dev3+68da829 # Copy package manifests (utilizing optional copy for Cargo files to be robust) COPY pyproject.toml README.md LICENSE NOTICE ./ @@ -67,7 +67,7 @@ FROM python:3.10-slim-bookworm # Define standard OCI build parameters ARG BUILD_DATE ARG GIT_SHA -ARG VERSION=0.1.0 +ARG VERSION=0.0.1.dev3+68da829 # OCI Metadata Labels LABEL org.opencontainers.image.created=$BUILD_DATE \ diff --git a/README.md b/README.md index d48a586..329ef70 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ + +

diff --git a/SECURITY.md b/SECURITY.md index 07c62a5..e4cffac 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,17 +1,31 @@ -# Security Policy for `rustarium` + + +# Security Policy for Rustarium -Please check the table below for the versions of `rustarium` that are currently being supported with security updates. +We take the security of Rustarium seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability. + +## Supported Versions -| Version | Supported | -| :------------------------------ | :----------------- | -| `{{current_major_version}}.x` | :white_check_mark: | -| `< {{current_major_version}}.0` | :x: | +Please check the table below for the versions of Rustarium that are currently being supported with security updates. -*(Note: Replace the table contents with your actual versioning scheme once released.)* +| Version | Supported | +| :-------- | :----------------- | +| `0.1.x` | :white_check_mark: | +| `< 0.1.0` | :x: | ## Reporting a Vulnerability @@ -21,7 +35,7 @@ Please check the table below for the versions of `rustarium` that are currently If you discover a security vulnerability, please bring it to our attention right away using one of the following methods: 1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/markurtz/rustarium/security/advisories)** of this repository. -1. **Email:** Send your report directly to **contact the maintainers**. +1. **Direct Message:** Send a message directly to the maintainer's GitHub user account, **[markurtz](https://github.com/markurtz)**, if applicable or through other provided direct pathways for the user. ### What to Include in Your Report @@ -31,7 +45,7 @@ To help us resolve the issue quickly, please include the following information: - **Detailed description** of the vulnerability and its potential impact. - **Step-by-step instructions** to reproduce the issue. - **Proof of Concept (PoC)** code or screenshots, if available. -- **Environment details** (e.g., version of `rustarium`, OS, Python version, relevant configurations). +- **Environment details** (e.g., version of Rustarium, OS, Python version, relevant configurations). ## Triage and Resolution Process @@ -46,13 +60,13 @@ We will handle your report with strict confidentiality. Our process is as follow **In Scope:** -- Vulnerabilities within the core `rustarium` codebase. +- Vulnerabilities within the core Rustarium codebase. - Security issues resulting from our default configurations or execution paths. **Out of Scope:** - Theoretical issues without a reproducible PoC. -- Vulnerabilities in third-party dependencies that are not exploitable through `rustarium`. -- Issues requiring the victim to intentionally clone and run `rustarium` against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. +- Vulnerabilities in third-party dependencies that are not exploitable through Rustarium. +- Issues requiring the victim to intentionally clone and run Rustarium against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. *(Note: We currently do not operate a bug bounty program. Disclosures are greatly appreciated but are not eligible for financial rewards at this time.)* diff --git a/SUPPORT.md b/SUPPORT.md index 60b9bcf..ff6fb32 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,6 +1,22 @@ -# Support for `rustarium` + + +# Support for Rustarium + +We are excited to have you use Rustarium! If you need help, please follow these guidelines to ensure you get support quickly and efficiently. ## Security Vulnerabilities @@ -25,10 +41,10 @@ If you cannot find an answer in the documentation or existing issues, please ope | :--------------------- | :-------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | **Bug Report** | [GitHub Issues](https://github.com/markurtz/rustarium/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | | **Feature Request** | [GitHub Issues](https://github.com/markurtz/rustarium/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | -| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/rustarium/discussions/new) | Start a discussion for questions about how to use `rustarium`, architecture queries, or advice. | +| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/rustarium/discussions/new) | Start a discussion for questions about how to use Rustarium or for general advice. | Feel free to join the conversation on GitHub Discussions to connect with other users and maintainers. ## Commercial Support -At this time, there is no official commercial support available for `rustarium`. Support is provided on a best-effort basis by the open-source community and maintainers. +At this time, there is no official commercial support available for Rustarium. Support is provided on a best-effort basis by the open-source community and maintainers. diff --git a/crates/rustarium-core/src/lib.rs b/crates/rustarium-core/src/lib.rs index 195a18a..2232637 100644 --- a/crates/rustarium-core/src/lib.rs +++ b/crates/rustarium-core/src/lib.rs @@ -1,17 +1,3 @@ -// Copyright 2026 markurtz -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - //! Core Python bindings for rustarium. use pyo3::prelude::*; diff --git a/crates/rustarium-core/src/sum.rs b/crates/rustarium-core/src/sum.rs index 74e4dec..08960ee 100644 --- a/crates/rustarium-core/src/sum.rs +++ b/crates/rustarium-core/src/sum.rs @@ -1,17 +1,3 @@ -// Copyright 2026 markurtz -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - //! Example module implementing sum logic and unit tests. use pyo3::prelude::*; diff --git a/crates/rustarium-core/tests/test_integration.rs b/crates/rustarium-core/tests/test_integration.rs index 88226b9..5d0960b 100644 --- a/crates/rustarium-core/tests/test_integration.rs +++ b/crates/rustarium-core/tests/test_integration.rs @@ -1,17 +1,3 @@ -// Copyright 2026 markurtz -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use rustarium_core::sum_as_string_internal; #[test] diff --git a/cst.yaml b/cst.yaml index 42ccb90..34a2a96 100644 --- a/cst.yaml +++ b/cst.yaml @@ -42,3 +42,27 @@ commandTests: - "--version" expectedOutput: - "rustarium v.*" + - name: "Verify rustarium CLI binary is available and prints version" + command: "rustarium" + args: + - "--version" + expectedOutput: + - "rustarium v.*" + - name: "Verify rustarium CLI binary help command works" + command: "rustarium" + args: + - "--help" + expectedOutput: + - "Usage: rustarium.*" + - "diagnose" + - "setup" + - name: "Verify rustarium module help command works" + command: "python" + args: + - "-m" + - "rustarium" + - "--help" + expectedOutput: + - "Usage: python -m rustarium.*" + - "diagnose" + - "setup" diff --git a/docs/guides/github-workflows.md b/docs/guides/github-workflows.md index f468147..2650f07 100644 --- a/docs/guides/github-workflows.md +++ b/docs/guides/github-workflows.md @@ -4,7 +4,7 @@ This guide explains the continuous integration and continuous deployment (CI/CD) ## Understand the architecture -The repository utilizes a modular, standardized GitHub Actions architecture. Workflows are categorized into core lifecycle events (e.g., development, main, nightly) and reusable helper templates (prefixed with `_`). +The repository utilizes a modular, standardized GitHub Actions architecture. Workflows are categorized into core lifecycle events (prefixed with `pipeline-`) and utility triggers (prefixed with `util-`). Our CI/CD pipelines ensure that all code merged into the `main` branch meets strict code quality, type-safety, testing, and security standards. @@ -12,23 +12,23 @@ Our CI/CD pipelines ensure that all code merged into the `main` branch meets str When contributing to this repository, your code will travel through the following pipeline stages. -### 1. The pull request cycle (`development.yml`) +### 1. The pull request cycle (`pipeline-development.yml`) -When you open a Pull Request against `main`, the `development.yml` workflow is triggered. This is the primary gateway for all code changes. +When you open a Pull Request against `main`, the `pipeline-development.yml` workflow is triggered. This is the primary gateway for all code changes. **What it enables:** -- **Quality Gates:** Runs `hatch run all:lint` (Ruff/mdformat/etc.) and `hatch run all:types` (Astral's ty/cargo check) to enforce consistent formatting and type safety. -- **Security Audits:** Scans for vulnerabilities in dependencies and code patterns. -- **Testing:** Runs the unit and integration test suites. The PR will be blocked from merging if any tests fail. -- **Documentation Previews:** Verifies that the documentation can be built cleanly. +- **Quality Gates:** Runs code quality and formatting checks (`hatch run python:lint`, `hatch run rust:lint`, `hatch run project:lint`, `hatch run oci:lint`) and static type verification (`hatch run python:types`, `hatch run rust:types`) to enforce consistent standards. +- **Security Audits:** Scans for vulnerabilities in dependencies and code patterns using `detect-secrets`, `cargo audit` (via Rust security action), and `trivy`/`dockle` (via OCI security checks). +- **Testing:** Runs the Python and Rust unit, integration, and OCI structure tests. The PR will be blocked from merging if any tests fail. +- **Documentation Previews:** Verifies that the Zensical documentation can be built cleanly. **Validation Standards:** -Your PR must pass all checks before it can be merged. We require strict adherence to PEP 8, full type annotations, and all tests must pass with adequate coverage. +Your PR must pass all checks before it can be merged. We require strict adherence to PEP 8/Ruff, Rust formatting, type annotations, and all tests must pass with adequate coverage. -### 2. Main branch validation (`main.yml`) +### 2. Main branch validation (`pipeline-main.yml`) -Once your PR is reviewed and merged into `main`, the `main.yml` workflow acts as a secondary validation layer. +Once your PR is reviewed and merged into `main`, the `pipeline-main.yml` workflow acts as a secondary validation layer. **What it enables:** @@ -40,32 +40,42 @@ Once your PR is reviewed and merged into `main`, the `main.yml` workflow acts as To catch configuration drift and conduct deeper analysis, we run scheduled workflows: -- **Nightly (`nightly.yml`):** Runs daily at 00:00 UTC. It performs extended regression testing, builds alpha container images, and does deep security analysis. -- **Weekly (`weekly.yml`):** Runs every Sunday at 00:00 UTC. Focused on dependency hygiene, checking for outdated constraints and ensuring the test suite is robust against external changes. +- **Nightly (`pipeline-nightly.yml`):** Runs daily at 00:00 UTC. It performs extended regression testing, builds alpha container images, and does deep security analysis. +- **Weekly (`pipeline-weekly.yml`):** Runs every Sunday at 00:00 UTC. Focused on dependency hygiene, checking for outdated constraints and ensuring the test suite is robust against external changes. -## Manage releases (`release.yml`) +## Manage releases (`pipeline-release.yml`) Releasing a new version is automated using Git tags. **What it enables:** -When a maintainer pushes a `v*.*.*` tag (e.g., `v1.2.0`), the `release.yml` workflow takes over. +When a maintainer pushes a `v*.*.*` tag (e.g., `v1.2.0`), the `pipeline-release.yml` workflow takes over. - It performs a final, full verification of the entire test suite. -- It builds immutable Python packages (sdist and wheel). +- It builds immutable Python packages (sdist and wheel, compiling native Rust bindings using Maturin). - It attests the build provenance using OIDC. - It publishes the artifacts to **PyPI** and the container image to the **GitHub Container Registry (GHCR)**. -## Utilize reusable templates - -To maintain consistency and reduce duplication, our lifecycle pipelines rely on modular templates located in the `.github/workflows/` directory. If you need to add new checks, you generally modify these templates rather than the lifecycle workflows directly. - -- `_tests.yml`: Orchestrates test suite execution (unit, integration, e2e). -- `_quality.yml`: Defines the exact linting and type-checking commands. -- `_security.yml`: Configures dependency scanning and SAST tooling. -- `_docs.yml`: Handles building documentation using Zensical. -- `_build_package.yml`: Standardizes artifact generation. - -By relying on templates, we ensure that the exact same linting rules are applied whether you are testing a PR, running a nightly build, or deploying a release. +## Utilize custom actions + +To maintain consistency and reduce duplication, our lifecycle pipelines rely on composite actions located in the `.github/actions/` directory: + +- **Python Environment:** + - `python/tests`: Orchestrates Python test suite execution (unit, integration, e2e). + - `python/quality`: Defines the exact linting (Ruff) and type-checking (Mypy/Ty) commands. + - `python/build` and `python/publish`: Standardizes Python wheel generation (compiling PyO3 Rust extensions) and PyPI distribution. +- **Rust Environment:** + - `rust/tests`: Runs Rust-specific unit and integration tests. + - `rust/quality`: Enforces Rust formatting checks and Clippy rules. + - `rust/security`: Audits cargo dependencies for vulnerabilities. +- **OCI Containers:** + - `oci/tests`: Runs OCI Container Structure Tests (CST). + - `oci/quality`: Lint Dockerfiles with Hadolint and Docker Compose configurations. + - `oci/build` and `oci/publish`: Builds and registers production-ready container images. +- **Project-wide Utilities:** + - `project/quality`: Enforces repository-wide guidelines (mdformat, yamlfix, taplo). + - `project/docs`: Handles building MkDocs/Zensical sites. + +By relying on these shared actions, we ensure that the exact same linting and test rules are applied whether you are testing a PR, running a nightly build, or deploying a release. ______________________________________________________________________ diff --git a/docs/index.md b/docs/index.md index 20524b1..a35c9bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,21 +4,37 @@ hide: - toc --- + +

- - - - rustarium Logo - +rustarium Logo +rustarium Logo # rustarium **A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust.** [Get Started](getting-started/index.md){ .md-button .md-button--primary } -View on GitHub +[View on GitHub](https://github.com/markurtz/rustarium){ .md-button }
@@ -26,11 +42,8 @@ hide: ## Overview

- - - - User Flow Diagram - + User Flow Diagram + User Flow Diagram

## What's Included @@ -115,8 +128,8 @@ For advanced installation options, and step-by-step onboarding, see the [Install ## Links -- :material-github: GitHub Repository -- :material-map-marker-path: Roadmap +- :material-github: [GitHub Repository](https://github.com/markurtz/rustarium) +- :material-map-marker-path: [Roadmap](https://github.com/markurtz/rustarium/milestones) diff --git a/docs/reference/index.md b/docs/reference/index.md index 9a04c2d..4785cac 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -10,7 +10,7 @@ The Reference section contains the complete technical documentation for `rustari Command-line usage and subcommands. -- **[:material-api: Python API](python_api.md)** +- **[:material-api: Python API](python_api/index.md)** Complete public Python API documentation. diff --git a/docs/reference/python_api.md b/docs/reference/python_api.md deleted file mode 100644 index 2bdaa98..0000000 --- a/docs/reference/python_api.md +++ /dev/null @@ -1,5 +0,0 @@ -# Python API - -This page documents the public Python API for `rustarium`. - -::: rustarium diff --git a/docs/scripts/gen_ref_pages.py b/docs/scripts/gen_ref_pages.py new file mode 100644 index 0000000..9b4762c --- /dev/null +++ b/docs/scripts/gen_ref_pages.py @@ -0,0 +1,155 @@ +"""Manage dynamic Python API reference pages in the docs directory. + +This module automates the discovery of source code files and generates corresponding +Markdown files containing mkdocstrings directives. This setup ensures that the +project's Python API reference documentation is dynamically built, cleanly organized, +and synchronized with source changes. + +Example: + To programmatically trigger a reference page build: + + .. code-block:: python + + from pathlib import Path + from gen_ref_pages import generate + + generate(src_dir=Path("src"), ref_dir=Path("docs/reference/python_api")) +""" + +from __future__ import annotations + +import shutil +from pathlib import Path +from typing import Annotated + +import typer + +__all__ = [ + "REF_DIR", + "SRC_DIR", + "app", + "clean", + "generate", +] + +SRC_DIR: Annotated[ + Path, + "The default root directory containing the project source code to scan.", +] = Path("src") +REF_DIR: Annotated[ + Path, + "The default target directory where markdown reference files will be written.", +] = Path("docs/reference/python_api") + +app: Annotated[ + typer.Typer, + "The Typer application instance orchestrating command-line entry points " + "for reference documentation management.", +] = typer.Typer(help="Manage dynamic Python API reference pages.") + + +@app.command() +def clean( + ref_dir: Annotated[ + Path, + typer.Option( + "--ref-dir", + help="Reference directory to clean.", + ), + ] = REF_DIR, +) -> None: + """Purge the generated reference directory to prevent stale pages. + + This utility removes all dynamically generated reference files to ensure + that modules deleted or renamed in the source directory do not leave + stale reference documentation behind in the build output. + + Example: + .. code-block:: python + + from pathlib import Path + from gen_ref_pages import clean + + clean(ref_dir=Path("docs/reference/python_api")) + + :param ref_dir: The target reference directory path to delete. + """ + if ref_dir.exists(): + shutil.rmtree(ref_dir) + + +@app.command() +def generate( + src_dir: Annotated[ + Path, + typer.Option( + "--src-dir", + help="Source directory containing Python code.", + ), + ] = SRC_DIR, + ref_dir: Annotated[ + Path, + typer.Option( + "--ref-dir", + help="Reference directory where documentation is generated.", + ), + ] = REF_DIR, +) -> None: + """Generate Python API reference pages dynamically in the docs directory. + + This function scans the source directory for Python modules and creates a + corresponding reference Markdown file containing the appropriate + mkdocstrings directive. It automatically excludes `__main__` entry points + and handles package `__init__` files to build a clean documentation hierarchy. + + Example: + .. code-block:: python + + from pathlib import Path + from gen_ref_pages import generate + + generate(src_dir=Path("src"), ref_dir=Path("docs/reference/python_api")) + + :param src_dir: The root directory containing Python source modules. + :param ref_dir: The destination directory to write Markdown reference pages. + """ + clean(ref_dir=ref_dir) + ref_dir.mkdir(parents=True, exist_ok=True) + + for source_path in sorted(src_dir.rglob("*.py")): + module_path = source_path.relative_to(src_dir).with_suffix("") + module_parts = list(module_path.parts) + + # Exclude __main__ command line entry points or non-public modules + if module_parts[-1] == "__main__": + continue + + is_index = False + if module_parts[-1] == "__init__": + module_parts.pop() + is_index = True + + if not module_parts: + continue + + import_str = ".".join(module_parts) + + # Strip the root package name to keep the navigation clean + doc_parts = module_parts[1:] + + if is_index: + doc_path = ref_dir.joinpath(*doc_parts, "index.md") + else: + doc_path = ref_dir.joinpath(*doc_parts).with_suffix(".md") + + # Ensure parent directories exist + doc_path.parent.mkdir(parents=True, exist_ok=True) + + # Generate the file with mkdocstrings directive + with doc_path.open("w", encoding="utf-8") as doc_file: + doc_file.write(f"# {import_str}\n\n") + doc_file.write(f"::: {import_str}\n") + + +if __name__ == "__main__": + app() diff --git a/docs/scripts/macros.py b/docs/scripts/macros.py index 8b90cb1..5d6fc89 100644 --- a/docs/scripts/macros.py +++ b/docs/scripts/macros.py @@ -1,17 +1,3 @@ -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """ Custom Zensical macros for markdown document processing. diff --git a/examples/README.md b/examples/README.md index f9758a8..1e35182 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,25 @@ -# rustarium Examples + + +# Rustarium Examples + +This directory contains practical, runnable demonstrations of how to use Rustarium in various scenarios. These examples are designed to help you quickly understand core concepts, advanced configurations, and best practices. ## Prerequisites @@ -14,13 +33,9 @@ Before running the examples, ensure you have set up your environment correctly: ## Example Index -Below is a curated list of available examples, categorized by complexity: - -| Example | Complexity | Description | -| :------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------- | -| **`[example_template/](example_template/)`** | Beginner | A generic template demonstrating standard structure, configuration, and telemetry integration. | +Below is a curated list of available examples, categorized by build tool and use case: - + ## Running the Examples @@ -28,7 +43,7 @@ Most examples can be executed directly from the command line. Navigate to the ro ```bash # Example: Running a generic example script -python examples/example_template/main.py +python examples/example_name/main.py ``` > [!TIP] diff --git a/examples/__init__.py b/examples/__init__.py index e69de29..37c5ce5 100644 --- a/examples/__init__.py +++ b/examples/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Unless otherwise noted, all files in this directory and its subdirectories +# are licensed under the Apache License, Version 2.0. + diff --git a/.github/actions/rust/publish/action.yml b/examples/conftest.py similarity index 56% rename from .github/actions/rust/publish/action.yml rename to examples/conftest.py index 54949cf..1200246 100644 --- a/.github/actions/rust/publish/action.yml +++ b/examples/conftest.py @@ -1,4 +1,3 @@ ---- # Copyright 2026 markurtz # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,13 +11,23 @@ # 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. -name: "Rust Publish" -description: "Placeholder action (Rust bindings are published inside python wheels)" -runs: - using: "composite" - steps: - - name: Rust Publish Info - shell: bash - run: |- - echo "[INFO] Rust bindings publication is managed via the python package wheel distribution." - echo "[INFO] See python/publish action for publishing to PyPI." +# +# Unless otherwise noted, all files in this directory and its subdirectories +# are licensed under the Apache License, Version 2.0. + +""" +Shared conftest configuration for examples tests. +""" + +from __future__ import annotations + +from typing import Any + + +def pytest_configure(config: Any) -> None: + """ + Configure environment variables and handlers before test suite runs. + + :param config: The pytest Config object. + """ + _ = config diff --git a/examples/example_template/test_example.py b/examples/example_template/test_example.py new file mode 100644 index 0000000..dfdec76 --- /dev/null +++ b/examples/example_template/test_example.py @@ -0,0 +1,15 @@ +""" +Test suite for the example template. +""" + +from __future__ import annotations + +from examples.example_template.main import main + + +def test_example_runs() -> None: + """ + Ensure that the example's main function executes without raising errors. + """ + # Simply run the main function to verify it works + main() diff --git a/llms-full.txt b/llms-full.txt index 94a2d09..83f9f60 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -6,26 +6,1752 @@ This file contains the concatenated core documentation for the project to provid > A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust. -`rustarium` is designed to eliminate boilerplate and enforce consistency across an organization's repositories. -For comprehensive context and full documentation, please see [llms-full.txt](llms-full.txt). +`rustarium` provides secure execution environments, logging, and metrics for Python and WebAssembly processes. +For complete, merged context containing the full codebase and all guides, see [llms-full.txt](llms-full.txt). -## Docs -- [Home](https://markurtz.github.io/rustarium/): Documentation site home page -- [Getting Started](https://markurtz.github.io/rustarium/getting-started/): Installation, quickstart, and workflow guides +## Primary Documentation Links +- [Documentation Home](https://markurtz.github.io/rustarium/): Main site home page +- [Getting Started](https://markurtz.github.io/rustarium/getting-started/): Onboarding, installation, and setup - [Guides](https://markurtz.github.io/rustarium/guides/): How-to guides for common tasks -- [API Reference](https://markurtz.github.io/rustarium/reference/): Full API reference documentation +- [API Reference](https://markurtz.github.io/rustarium/reference/): Complete API reference + +## Architectural Topography + +### Configuration & Entrypoints +- [pyproject.toml](pyproject.toml): Project settings, environment scripts, and Hatch metadata. +- [AGENTS.md](AGENTS.md): Foundational agent development guidelines, build commands, and security boundaries. +- [CLAUDE.md](CLAUDE.md): Direct pointer routing assistants to `AGENTS.md`. +- [src/rustarium/__main__.py](src/rustarium/__main__.py): Standalone CLI entrypoint. + +### Core Python Modules +- [src/rustarium/settings.py](src/rustarium/settings.py): Pydantic schemas validating configuration settings. +- [src/rustarium/logging.py](src/rustarium/logging.py): Loguru logging setup and environment metrics. +- [src/rustarium/client.py](src/rustarium/client.py): Python orchestrator client interface. +- [src/rustarium/exceptions.py](src/rustarium/exceptions.py): Orchestrator-specific exception types. + +### Core Rust Extension (Maturin Bindings) +- [Cargo.toml](Cargo.toml): Cargo workspace manifest. +- [crates/rustarium-core/Cargo.toml](crates/rustarium-core/Cargo.toml): Core Rust crate manifest. +- [crates/rustarium-core/src/lib.rs](crates/rustarium-core/src/lib.rs): Rust module bindings and PyO3 setup. +- [crates/rustarium-core/src/sum.rs](crates/rustarium-core/src/sum.rs): Sandboxed computation functions. + +### Testing Suite Tiers +- [tests/python/unit/](tests/python/unit/): Python unit test suite. +- [tests/python/integration/](tests/python/integration/): Python integration test suite. +- [tests/e2e/](tests/e2e/): High-level black-box orchestrator integration tests. + +## File: pyproject.toml + +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[build-system] +requires = ["gitversioned[maturin]>=0.1.0", "maturin>=1.5,<2.0"] +build-backend = "gitversioned.plugins.maturin_plugin" + +[project] +name = "rustarium" +dynamic = ["version"] +description = "A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust." +readme = "README.md" +requires-python = ">=3.10" +license = { text = "Apache-2.0" } +authors = [{ name = "Mark Kurtz" }] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dependencies = [ + "loguru~=0.7", + "packaging>=26.0", + "pydantic~=2.0", + "pydantic-settings~=2.0", + "setuptools>=64.0.0", + "tomli~=2.0; python_version < '3.11'", + "tstr>=0.4.1", + "typer>=0.12", +] + +[project.optional-dependencies] +hatch = ["hatchling~=1.18"] +maturin = ["maturin>=1.0,<2.0"] +opentelemetry = ["opentelemetry-api>=1.0.0", "opentelemetry-sdk>=1.0.0"] + +[dependency-groups] +test = [ + "pytest>=9.0.3,<10", + "pytest-asyncio>=1.4.0,<2", + "pytest-cov>=7.1.0,<8", + "pytest-mock>=3.15.1,<4", + "respx>=0.23.1,<1", + "pytest-xdist>=3.5,<4", + "build", + "wheel", + "maturin>=1.0,<2.0", +] +lint = [ + "ty", + "ruff>=0.15,<1", + "mdformat>=1.0,<2", + "mdformat-frontmatter>=2.0,<3", + "mdformat-gfm>=1.0,<2", + "mdformat-footnote>=0.1.1,<1", + "mdformat-gfm-alerts>=1.0.1,<3", + "yamllint>=1.35,<2", + "urlchecker>=0.0.35", + "phmdoctest>=1.4,<2", + "yamlfix>=1.19,<2", + "taplo", +] +docs = [ + "zensical>=0.0.40", + "mkdocs-gen-files>=0.5.0", + "mkdocstrings>=0.24.0", + "mkdocstrings-python>=1.9.0", + # Temporary bridge: Zensical compatible fork of mike. + # Update to Zensical native versioning once stabilized. + "mike @ git+https://github.com/squidfunk/mike.git", +] +security = [ + "detect-secrets>=1.5,<2", + "checkov>=3.0,<4", + "semgrep>=1.73,<2", + "pip-audit>=2.7,<3", +] +environment = ["hatch>=1.12.0"] +dev = [ + { include-group = "test" }, + { include-group = "lint" }, + { include-group = "docs" }, + { include-group = "security" }, + { include-group = "environment" }, +] + +[project.scripts] +rustarium = "rustarium.__main__:main" + +[project.urls] +Homepage = "https://github.com/markurtz/rustarium" +Repository = "https://github.com/markurtz/rustarium.git" +Issues = "https://github.com/markurtz/rustarium/issues" +Documentation = "https://markurtz.github.io/rustarium/" + + +# ============================================================================== +# Maturin Crate Configurations +# ============================================================================== + +[tool.maturin] +features = ["pyo3/extension-module"] +manifest-path = "crates/rustarium-core/Cargo.toml" +module-name = "rustarium._rust" +python-source = "src" +include = ["src/rustarium/version.py"] + + +# ============================================================================== +# Versioning +# ============================================================================== + +[tool.gitversioned] +output = "src/rustarium/version.py" +source_type = ["tag"] +format_dev = "dev{ref.total_commits}+{ref.short_sha}" +format_pre = "a{ref.timestamp:%Y%m%d}" +output_strategies = { type = "regex", pattern = '(?m)^__version__\s*=\s*"(?P[^"]+)"' } + +[tool.gitversioned.auto_increment] +pre = "minor" +dev = "patch" + +[tool.gitversioned.overrides.cargo] +output = "Cargo.toml" +version_standard = "semver2" +output_strategies = { type = "regex", pattern = '''(?ms)^\[workspace\.package\].*?^(\s*version\s*=\s*)(["'])(?P[^"']+)\2''' } + +[tool.gitversioned.overrides.docker] +output = "Dockerfile" +output_strategies = { type = "regex", pattern = '''(?m)^(\s*ARG\s+VERSION\s*=\s*)(?P[^\s\n]+)''' } + + +# ============================================================================== +# Hatch Environment Configurations +# ============================================================================== + +[tool.hatch.envs.default] +dependency-groups = ["dev"] + +[tool.hatch.envs.default.env-vars] +COVERAGE_CORE = "pytrace" +GITVERSIONED__LOGGING__LEVEL = "ERROR" +PYTHONPATH = "." +PYTHON_TARGETS = "docs examples scripts src tests" +RUST_MANIFEST = "crates/rustarium-core/Cargo.toml" +OCI_IMAGE = "rustarium:latest" +MDFORMAT_TARGETS = ".devcontainer/ .github/ crates/ docs/*.md docs/community/ docs/examples/ docs/getting-started/ docs/guides/ examples/ scripts/ src/ tests/ *.md" +YAML_TARGETS = ".devcontainer/ .github/ crates/ docs/ examples/ scripts/ src/ tests/ *.yml *.yaml" +TOML_TARGETS = ".devcontainer/ .github/ crates/ docs/ examples/ scripts/ src/ tests/ *.toml" +PYTHON_SRC = "rustarium" +PYTHON_UNIT_TESTS = "tests/python/unit" +PYTHON_INT_TESTS = "tests/python/integration" +PYTHON_COV_DIR = "coverage/python" +RUST_COV_DIR = "coverage/rust" +E2E_TESTS = "tests/e2e" +DOC_TESTS_PATH = ".tests/docs" +SECRETS_BASELINE = ".detect-secrets.scan.json" +RUN_OCI_SCRIPT = "scripts/run_oci.py" +CHECK_LINKS_SCRIPT = "scripts/check_links.py" +GENERATE_DOC_TESTS_SCRIPT = "scripts/generate_doc_tests.py" + +[tool.hatch.envs.all] +template = "default" +builder = true + +[tool.hatch.envs.all.scripts] +lint = [ + "hatch run python:lint {args}", + "hatch run rust:lint {args}", + "hatch run oci:lint {args}", + "hatch run project:lint {args}", +] +format = [ + "hatch run python:format {args}", + "hatch run rust:format {args}", + "hatch run oci:format {args}", + "hatch run project:format {args}", +] +types = [ + "hatch run python:types {args}", + "hatch run rust:types {args}", + "hatch run oci:types {args}", + "hatch run project:types {args}", +] +security = [ + "hatch run python:security {args}", + "hatch run rust:security {args}", + "hatch run oci:security {args}", + "hatch run project:security {args}", +] +quality = [ + "hatch run python:quality {args}", + "hatch run rust:quality {args}", + "hatch run oci:quality {args}", + "hatch run project:quality {args}", +] +build = [ + "hatch build {args}", + "hatch run oci:build {args}", + "hatch run project:docs {args}", +] +tests = ["hatch run tests-func {args}", "hatch run tests-e2e {args}"] +tests-cov = [ + "hatch run tests-func-cov {args}", + "hatch run tests-e2e-cov {args}", +] +tests-func = [ + "hatch run python:tests-func {args}", + "hatch run rust:tests-func {args}", + "hatch run oci:tests-func {args}", + "hatch run project:tests-func {args}", +] +tests-func-cov = [ + "hatch run python:tests-func-cov {args}", + "hatch run rust:tests-func-cov {args}", + "hatch run oci:tests-func-cov {args}", + "hatch run project:tests-func-cov {args}", +] +tests-unit = [ + "hatch run python:tests-unit {args}", + "hatch run rust:tests-unit {args}", + "hatch run oci:tests-unit {args}", + "hatch run project:tests-unit {args}", +] +tests-unit-cov = [ + "hatch run python:tests-unit-cov {args}", + "hatch run rust:tests-unit-cov {args}", + "hatch run oci:tests-unit-cov {args}", + "hatch run project:tests-unit-cov {args}", +] +tests-int = [ + "hatch run python:tests-int {args}", + "hatch run rust:tests-int {args}", + "hatch run oci:tests-int {args}", + "hatch run project:tests-int {args}", +] +tests-int-cov = [ + "hatch run python:tests-int-cov {args}", + "hatch run rust:tests-int-cov {args}", + "hatch run oci:tests-int-cov {args}", + "hatch run project:tests-int-cov {args}", +] +tests-e2e = [ + "hatch run python:tests-e2e {args}", + "hatch run rust:tests-e2e {args}", + "hatch run oci:tests-e2e {args}", + "hatch run project:tests-e2e {args}", +] +tests-e2e-cov = [ + "hatch run python:tests-e2e-cov {args}", + "hatch run rust:tests-e2e-cov {args}", + "hatch run oci:tests-e2e-cov {args}", + "hatch run project:tests-e2e-cov {args}", +] +docs = [ + "hatch run python:docs {args}", + "hatch run rust:docs {args}", + "hatch run oci:docs {args}", + "hatch run project:docs {args}", +] +docs-serve = ["hatch run docs {args}", "hatch run project:docs-serve {args}"] + +# ============================================================================== +# Python Environment +# ============================================================================== +[tool.hatch.envs.python] +template = "default" +builder = true + +[tool.hatch.envs.py310] +template = "python" +python = "3.10" + +[tool.hatch.envs.python.scripts] +lint = [ + "ruff check {args:{env:PYTHON_TARGETS}}", + "ruff format --check {args:{env:PYTHON_TARGETS}}", +] +format = [ + "ruff check --fix {args:{env:PYTHON_TARGETS}}", + "ruff format {args:{env:PYTHON_TARGETS}}", +] +types = "ty check {args:{env:PYTHON_TARGETS}}" +security = [ + "semgrep scan --error {args}", + "pip-audit {args}", + "ruff check --select S {args:{env:PYTHON_TARGETS}}", +] +quality = [ + "hatch run python:format {args}", + "hatch run python:lint {args}", + "hatch run python:types {args}", + "hatch run python:security {args}", +] +tests-func = "python -m pytest {env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS} {args}" +tests-func-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "python -m pytest --cov={env:PYTHON_SRC} --cov-context=test --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-func.md {env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS} {args}", + "coverage report --contexts=\".*tests/python/unit/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-unit.md", + "coverage report --contexts=\".*tests/python/integration/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-int.md", + "python -c \"import pathlib; [p.write_text('\\n'.join(line for line in p.read_text().splitlines() if not line.startswith('Combined'))) for p in (pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-unit.md'), pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-int.md')) if p.exists()]\"", +] +tests-unit = "python -m pytest {env:PYTHON_UNIT_TESTS} {args}" +tests-unit-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "python -m pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-unit.md {env:PYTHON_UNIT_TESTS} {args}", +] +tests-int = "python -m pytest {env:PYTHON_INT_TESTS} {args}" +tests-int-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "python -m pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-int.md {env:PYTHON_INT_TESTS} {args}", +] +tests-e2e = [ + "hatch build", + "pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium", + "python -m pytest {env:E2E_TESTS} {args}", +] +tests-e2e-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "hatch build", + "pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium", + "python -m pytest --cov=rustarium --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-e2e.md {env:E2E_TESTS} {args}", +] +docs = [ + "python -c \"import pathlib; pathlib.Path('.docs').mkdir(exist_ok=True)\"", + "typer src/rustarium/__main__.py utils docs --name rustarium --output .docs/cli.md", +] + +# ============================================================================== +# Rust Environment +# ============================================================================== +[tool.hatch.envs.rust] +template = "default" +post-install-commands = ["cargo install --locked cargo-run-bin"] + +[tool.hatch.envs.rust.scripts] +lint = [ + "cargo fmt --manifest-path {env:RUST_MANIFEST} --all -- --check {args}", + "cargo clippy --manifest-path {env:RUST_MANIFEST} --all-targets -- -D warnings {args}", +] +format = [ + "cargo fmt --manifest-path {env:RUST_MANIFEST} --all {args}", + "cargo clippy --manifest-path {env:RUST_MANIFEST} --fix --allow-dirty --allow-staged {args}", +] +types = "cargo check --manifest-path {env:RUST_MANIFEST} --all-targets {args}" +security = ["cargo bin cargo-audit {args}", "cargo bin cargo-deny check {args}"] +install-tools = ["cargo install --locked cargo-run-bin"] +quality = [ + "hatch run rust:format {args}", + "hatch run rust:lint {args}", + "hatch run rust:types {args}", + "hatch run rust:security {args}", +] +tests-func = "cargo test --manifest-path {env:RUST_MANIFEST} --all-targets {args}" +tests-func-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --all-targets --json --output-path {env:RUST_COV_DIR}/coverage_tests-func.json {args}", + "covgate check {env:RUST_COV_DIR}/coverage_tests-func.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-func.md", +] +tests-unit = "cargo test --manifest-path {env:RUST_MANIFEST} --lib --bins {args}" +tests-unit-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --lib --bins --json --output-path {env:RUST_COV_DIR}/coverage_tests-unit.json {args}", + "covgate check {env:RUST_COV_DIR}/coverage_tests-unit.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-unit.md", +] +tests-int = "cargo test --manifest-path {env:RUST_MANIFEST} --test '*' {args}" +tests-int-cov = [ + "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", + "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --test '*' --json --output-path {env:RUST_COV_DIR}/coverage_tests-int.json {args}", + "covgate check {env:RUST_COV_DIR}/coverage_tests-int.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-int.md", +] +tests-e2e = "echo '[INFO] No E2E tests are defined for the Rust environment' {args}" +tests-e2e-cov = [ + "hatch run rust:tests-e2e {args}", + "echo '[INFO] No coverage analysis is applicable for E2E tests in the Rust environment'", +] +docs = [ + "cargo doc --manifest-path {env:RUST_MANIFEST} --target-dir .docs/rust_api --no-deps --workspace {args}", +] + +# ============================================================================== +# OCI Environment +# ============================================================================== +[tool.hatch.envs.oci] +template = "default" + +[tool.hatch.envs.oci.scripts] +lint = [ + "python {env:RUN_OCI_SCRIPT} hadolint {args}", + "python {env:RUN_OCI_SCRIPT} compose-config {args}", + "python {env:RUN_OCI_SCRIPT} dclint {args}", +] +format = "python {env:RUN_OCI_SCRIPT} dclint --fix {args}" +types = "echo '[INFO] Type checking is not applicable for the OCI environment' {args}" +security = [ + "python {env:RUN_OCI_SCRIPT} dockle {args}", + "python {env:RUN_OCI_SCRIPT} trivy {args}", +] +quality = [ + "hatch run oci:format {args}", + "hatch run oci:lint {args}", + "hatch run oci:types {args}", + "hatch run oci:security {args}", +] +tests-func = [ + "hatch run oci:tests-unit {args}", + "hatch run oci:tests-int {args}", +] +tests-func-cov = [ + "hatch run oci:tests-unit-cov {args}", + "hatch run oci:tests-int-cov {args}", +] +tests-unit = "echo '[INFO] No unit tests are defined for the OCI environment' {args}" +tests-unit-cov = [ + "hatch run oci:tests-unit {args}", + "echo '[INFO] No coverage analysis is applicable for unit tests in the OCI environment'", +] +tests-int = "echo '[INFO] No integration tests are defined for the OCI environment' {args}" +tests-int-cov = [ + "hatch run oci:tests-int {args}", + "echo '[INFO] No coverage analysis is applicable for integration tests in the OCI environment'", +] +tests-e2e = "python {env:RUN_OCI_SCRIPT} cstest {args}" +tests-e2e-cov = [ + "hatch run oci:tests-e2e {args}", + "echo '[INFO] No coverage analysis is applicable for E2E tests in the OCI environment'", +] +docs = "echo '[INFO] No documentation is defined for the OCI environment' {args}" +build = "docker build -t {env:OCI_IMAGE} . {args}" + +# ============================================================================== +# Project Environment +# ============================================================================== +[tool.hatch.envs.project] +template = "default" + +[tool.hatch.envs.project.scripts] +lint = [ + "mdformat --check {args:{env:MDFORMAT_TARGETS}}", + "yamlfix --check {args:{env:YAML_TARGETS}}", + "yamllint {args:{env:YAML_TARGETS}}", + "taplo check {args:{env:TOML_TARGETS}}", +] +format = [ + "mdformat {args:{env:MDFORMAT_TARGETS}}", + "yamlfix {args:{env:YAML_TARGETS}}", + "taplo fmt {args:{env:TOML_TARGETS}}", +] +types = "echo '[INFO] Type checking is not applicable for the project environment' {args}" +security = [ + "detect-secrets scan --baseline {env:SECRETS_BASELINE} {args:.}", + "python -m checkov.main --quiet {args:-d .}", +] +quality = [ + "hatch run project:format {args}", + "hatch run project:lint {args}", + "hatch run project:types {args}", + "hatch run project:security {args}", +] +security-update = ["detect-secrets scan {args:.} > {env:SECRETS_BASELINE}"] +tests-func = [ + "hatch run project:tests-unit {args}", + "hatch run project:tests-int {args}", +] +tests-func-cov = [ + "hatch run project:tests-unit-cov {args}", + "hatch run project:tests-int-cov {args}", +] +tests-unit = "echo '[INFO] No unit tests are defined for the project environment'" +tests-unit-cov = [ + "hatch run project:tests-unit {args}", + "echo '[INFO] No coverage analysis is applicable for unit tests in the project environment'", +] +tests-int = [ + "python {env:GENERATE_DOC_TESTS_SCRIPT} {args:{env:PYTHON_TARGETS}}", + "python -m pytest {env:DOC_TESTS_PATH} {args}", +] +tests-int-cov = [ + "hatch run project:tests-int {args}", + "echo '[INFO] No coverage analysis is applicable for integration tests in the project environment'", +] +link-checks = "python {env:CHECK_LINKS_SCRIPT} {args:{env:MDFORMAT_TARGETS}}" +link-checks-cov = [ + "hatch run project:link-checks {args}", + "echo '[INFO] No coverage analysis is applicable for link checks in the project environment'", +] +tests-e2e = ["python -m pytest --import-mode=importlib examples {args}"] +tests-e2e-cov = [ + "hatch run project:tests-e2e {args}", + "echo '[INFO] No coverage analysis is applicable for E2E tests in the project environment'", +] +docs = [ + "python docs/scripts/gen_ref_pages.py generate", + "python -m zensical {args:build}", + "python -c \"import shutil, pathlib; src = pathlib.Path('.docs/rust_api/doc'); dst = pathlib.Path('site/reference/rust_api'); dst.mkdir(parents=True, exist_ok=True); [shutil.copytree(p, dst / p.name, dirs_exist_ok=True) if p.is_dir() else shutil.copy2(p, dst / p.name) for p in src.glob('*')] if src.exists() else None\"", +] +docs-serve = [ + "python docs/scripts/gen_ref_pages.py generate", + "python -m zensical serve {args}", +] + + +[tool.ty.src] +include = ["src/rustarium", "tests"] + +[tool.ruff] +line-length = 88 +indent-width = 4 +exclude = [ + "build", + "dist", + "env", + ".venv", + "src/rustarium/version.py", + ".tests", +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + +[tool.ruff.lint] +ignore = [ + "COM812", # ignore trailing comma errors due to older Python versions + "ISC001", # implicit string concatenation (often disabled when using ruff format) + "PD011", # ignore .values usage since ruff assumes it's a Pandas DataFrame + "PLR0913", # ignore too many arguments in function definitions + "PLW1514", # allow Path.open without encoding + "RET505", # allow `else` blocks + "RET506", # allow `else` blocks + "S311", # allow standard pseudo-random generators + "TC001", # ignore imports used only for type checking + "TC002", # ignore imports used only for type checking + "TC003", # ignore imports used only for type checking +] +select = [ + # Rules reference: https://docs.astral.sh/ruff/rules/ + + # Code Style / Formatting + "E", # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length + "W", # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length + "A", # flake8-builtins: prevents shadowing of Python built-in names + "C", # Convention: ensures code adheres to specific style and formatting conventions + "COM", # flake8-commas: enforces the correct use of trailing commas + "ERA", # eradicate: detects commented-out code that should be removed + "I", # isort: ensures imports are sorted in a consistent manner + "ICN", # flake8-import-conventions: enforces import conventions for better readability + "N", # pep8-naming: enforces PEP 8 naming conventions for classes, functions, and variables + "NPY", # NumPy: enforces best practices for using the NumPy library + "PD", # pandas-vet: enforces best practices for using the pandas library + "PT", # flake8-pytest-style: enforces best practices and style conventions for pytest tests + "PTH", # flake8-use-pathlib: encourages the use of pathlib over os.path for file system operations + "Q", # flake8-quotes: enforces consistent use of single or double quotes + "TCH", # flake8-type-checking: enforces type checking practices and standards + "TID", # flake8-tidy-imports: enforces tidy and well-organized imports + "RUF022", # flake8-ruff: enforce sorting of __all__ in modules + + # Code Structure / Complexity + "C4", # flake8-comprehensions: improves readability and performance of list, set, and dict comprehensions + "C90", # mccabe: checks for overly complex code using cyclomatic complexity + "ISC", # flake8-implicit-str-concat: prevents implicit string concatenation + "PIE", # flake8-pie: identifies and corrects common code inefficiencies and mistakes + "R", # Refactor: suggests improvements to code structure and readability + "SIM", # flake8-simplify: simplifies complex expressions and improves code readability + + # Code Security / Bug Prevention + "ARG", # flake8-unused-arguments: detects unused function and method arguments + "ASYNC", # flake8-async: identifies incorrect or inefficient usage patterns in asynchronous code + "B", # flake8-bugbear: detects common programming mistakes and potential bugs + "BLE", # flake8-blind-except: prevents blind exceptions that catch all exceptions without handling + "F", # Pyflakes: detects unused imports, shadowed imports, undefined variables, and various formatting errors in string operations + "INP", # flake8-no-pep420: prevents implicit namespace packages by requiring __init__.py + "PGH", # pygrep-hooks: detects deprecated and dangerous code patterns + "PL", # Pylint: comprehensive source code analyzer for enforcing coding standards and detecting errors + "RSE", # flake8-raise: ensures exceptions are raised correctly + "S", # flake8-bandit: detects security issues and vulnerabilities in the code + "SLF", # flake8-self: prevents incorrect usage of the self argument in class methods + "T10", # flake8-debugger: detects the presence of debugging tools such as pdb + "T20", # flake8-print: detects print statements left in the code + "UP", # pyupgrade: automatically upgrades syntax for newer versions of Python + "YTT", # flake8-2020: identifies code that will break with future Python releases + + # Code Documentation + "FIX", # flake8-fixme: detects FIXMEs and other temporary comments that should be resolved +] + +[tool.ruff.lint.extend-per-file-ignores] +"tests/**/*.py" = [ + "S101", # asserts allowed in tests + "ARG", # Unused function args allowed in tests + "PLR2004", # Magic value used in comparison + "TCH002", # No import only type checking in tests + "SLF001", # enable private member access in tests + "S105", # allow hardcoded passwords in tests + "S106", # allow hardcoded passwords in tests + "S311", # allow standard pseudo-random generators in tests + "PT011", # allow generic exceptions in tests + "N806", # allow uppercase variable names in tests + "PGH003", # allow general ignores in tests + "PLR0915", # allow complex statements in tests + "S603", # allow subprocess with untrusted input in tests + "S607", # allow partial executable paths in tests + "T201", # allow print in tests +] +"examples/**/*.py" = [ + "T201", # allow print in examples + "INP001", # allow implicit namespace packages + "S603", # allow subprocess check with untrusted input + "S607", # allow starting a process with a partial executable path + "S101", # allow asserts in tests/examples +] +"scripts/**/*.py" = [ + "T201", # allow print in scripts + "INP001", # allow implicit namespace packages + "S603", # allow subprocess check with untrusted input + "S607", # allow starting a process with a partial executable path + "PLC0415", # allow local imports (e.g. dynamic imports in build_docs) + "BLE001", # allow catching blind Exception +] +"docs/**/*.py" = [ + "T201", # allow print in docs + "INP001", # allow implicit namespace packages +] + +[tool.ruff.lint.isort] +known-first-party = ["rustarium", "tests"] + +[tool.pytest.ini_options] +addopts = "-s -vvv --cache-clear" +asyncio_mode = "auto" +log_level = "WARNING" +markers = [ + "smoke: quick tests to check basic functionality", + "sanity: detailed tests to ensure major functions work correctly", + "regression: tests to ensure that new changes do not break existing functionality", +] +testpaths = ["tests"] + +[tool.yamlfix] +line_length = 88 +sequence_style = "block_style" +preserve_quotes = true + +[tool.coverage.run] +patch = ["subprocess"] + +## File: taplo.toml + +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include = [ + "*.toml", + ".devcontainer/**/*.toml", + ".github/**/*.toml", + "crates/**/*.toml", + "docs/**/*.toml", + "examples/**/*.toml", + "scripts/**/*.toml", + "src/**/*.toml", + "tests/**/*.toml", +] + +## File: .yamllint + +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +extends: default + +ignore: | + .venv/ + venv/ + target/ + build/ + dist/ + +rules: + line-length: + max: 300 + truthy: + check-keys: false + document-start: disable + +## File: src/rustarium/__init__.py + +""" +Core initialization module for the rustarium package. + +This module serves as the primary entry point for the library, exposing the public +API components such as logging settings, application configuration, and version +metadata for convenient access by downstream consumers. +""" + +from __future__ import annotations + +from .client import AsyncRustariumClient, RustariumClient +from .exceptions import ( + ConfigurationError, + OrchestrationError, + RustariumError, + SecurityError, + WorkerError, +) +from .logging import LoggingSettings, configure_logger, logger +from .schemas import ( + ConcurrentConfig, + ConstantRateConfig, + JobConfig, + JobContext, + Metrics, + PoissonConfig, + RustariumContext, + SecurityProfile, + UpdatePayload, + ViolationPolicy, + Workload, + WorkloadStatus, +) +from .settings import Settings +from .version import __version__ + +try: + from ._rust import sum_as_string +except ImportError: + + def sum_as_string(a: int, b: int) -> str: + """Fallback sum function when compiled extension is not available.""" + return str(a + b) + + +__all__ = [ + "AsyncRustariumClient", + "ConcurrentConfig", + "ConfigurationError", + "ConstantRateConfig", + "JobConfig", + "JobContext", + "LoggingSettings", + "Metrics", + "OrchestrationError", + "PoissonConfig", + "RustariumClient", + "RustariumContext", + "RustariumError", + "SecurityError", + "SecurityProfile", + "Settings", + "UpdatePayload", + "ViolationPolicy", + "WorkerError", + "Workload", + "WorkloadStatus", + "__version__", + "configure_logger", + "logger", + "sum_as_string", +] + +## File: src/rustarium/logging.py + +""" +Loguru-based logging configuration and environment settings for rustarium. + +This module provides a unified interface for configuring application-level logging +using loguru and Pydantic settings. It handles dynamic OpenTelemetry formatting, +across the codebase and build environments. +""" + +from __future__ import annotations + +import contextlib +import json +import sys +import traceback +from collections.abc import Callable +from typing import Any, ClassVar, Literal + +from loguru import logger +from pydantic import Field, field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + +from rustarium.compat import opentelemetry_trace + +__all__ = ["LoggingSettings", "configure_logger", "logger"] + +_HANDLER_ID: int | None = None + + +class LoggingSettings(BaseSettings): + """ + Settings model for configuring the loguru logging infrastructure. + + This Pydantic model loads configuration from environment variables prefixed + with ``RUSTARIUM__LOGGING__`` and provides typed fields for controlling + log output, formatting, and OpenTelemetry integration. + + Example: + .. code-block:: python + + from rustarium.logging import LoggingSettings, configure_logger + + settings = LoggingSettings(enabled=True, level="DEBUG") + configure_logger(settings) + """ + + enabled: bool = Field( + default=False, + description=( + "Whether to enable rustarium loguru logging across the application." + ), + ) + clear_loggers: bool = Field( + default=False, + description=( + "If true, removes all existing loguru handlers before configuring new ones." + ), + ) + sink: str | Any = Field( + default=sys.stdout, + description=( + "The output sink for log messages. Can be an object or string " + "alias ('stdout')." + ), + ) + level: str = Field( + default="INFO", + description="The minimum severity level for emitted log messages.", + ) + otel_formatting: Literal["auto", "enable", "disable"] = Field( + default="auto", + description=( + "Controls OpenTelemetry JSON formatting. 'auto' enables it if " + "otel is installed." + ), + ) + format: str | Callable[..., Any] | None = Field( + default="{time:HH:mm:ss} | {level: <8} | " + "{name}:{function} - {message}\n", + description=( + "The log format string or function to use when otel formatting is disabled." + ), + ) + filter: Any = Field( + default=True, + description=( + "Filters log records. Defaults to True to filter by the 'rustarium' prefix." + ), + ) + enqueue: bool = Field( + default=True, + description="Whether to enable thread-safe asynchronous logging.", + ) + kwargs: dict[str, Any] = Field( + default_factory=dict, + description=( + "Additional keyword arguments to pass directly to loguru's add() method." + ), + ) + + model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict( + env_prefix="RUSTARIUM__LOGGING__", + env_nested_delimiter="__", + ) + """Pydantic configuration dict dictating environment variable prefixes.""" + + @field_validator("sink", mode="before") + @classmethod + def _parse_sink(cls, value: Any) -> Any: + # Convert string aliases for stdout/stderr to actual objects. + if isinstance(value, str): + mapping = { + "stdout": sys.stdout, + "sys.stdout": sys.stdout, + "stderr": sys.stderr, + "sys.stderr": sys.stderr, + } + return mapping.get(value.lower(), value) + return value + + +def _otel_formatter(record: dict[str, Any]) -> str: + # Format the log record as an OpenTelemetry compliant JSON string. + trace_id = span_id = trace_flags = None + + if opentelemetry_trace: + span = opentelemetry_trace.get_current_span() + context = span.get_span_context() + if context.is_valid: + trace_id = format(context.trace_id, "032x") + span_id = format(context.span_id, "016x") + trace_flags = format(context.trace_flags, "02x") + + log_record = { + "timestamp": record["time"].isoformat(), + "severity_text": record["level"].name, + "body": record["message"], + "resource": {"service.name": "rustarium"}, + "attributes": { + "module": record["name"], + "function": record["function"], + "line": record["line"], + **record["extra"], + }, + } + + if record.get("exception"): + exception = record["exception"] + log_record["attributes"]["exception.type"] = exception.type.__name__ + log_record["attributes"]["exception.message"] = str(exception.value) + log_record["attributes"]["exception.stacktrace"] = "".join( + traceback.format_exception( + exception.type, exception.value, exception.traceback + ) + ) + + if trace_id: + log_record.update( + { + "trace_id": trace_id, + "span_id": span_id, + "trace_flags": trace_flags, + } + ) + + # Escape braces so loguru doesn't interpret the JSON string as a format string + return json.dumps(log_record).replace("{", "{{").replace("}", "}}") + "\n" + + +def configure_logger(settings: LoggingSettings | None = None) -> None: + """ + Initializes the loguru logger with the provided settings or from the environment. + + This function configures the global loguru logger instance based on the provided + ``LoggingSettings``. It handles enabling/disabling the logger, managing sinks, + and injecting the appropriate formatter (including OpenTelemetry). + + Example: + .. code-block:: python + + from rustarium.logging import configure_logger, LoggingSettings + + configure_logger(LoggingSettings(level="DEBUG")) + + :param settings: An optional instance of ``LoggingSettings``. If not provided, + settings are automatically loaded from the environment. + :return: None + :raises ImportError: If OpenTelemetry formatting is explicitly enabled but the + package is not installed. + """ + global _HANDLER_ID # noqa: PLW0603 + + settings = settings or LoggingSettings() + + if not settings.enabled: + logger.disable("rustarium") + return + + logger.enable("rustarium") + + if settings.clear_loggers: + logger.remove() + _HANDLER_ID = None + elif isinstance(_HANDLER_ID, int): + with contextlib.suppress(ValueError): + logger.remove(_HANDLER_ID) + _HANDLER_ID = None + + use_otel = settings.otel_formatting == "enable" or ( + settings.otel_formatting == "auto" and opentelemetry_trace is not None + ) + if settings.otel_formatting == "enable" and opentelemetry_trace is None: + raise ImportError( + "OpenTelemetry is not installed but 'otel_formatting' was set to 'enable'." + ) + + log_format = _otel_formatter if use_otel else settings.format + filter_val = "rustarium" if settings.filter is True else settings.filter + + if isinstance(filter_val, (list, tuple)): + prefixes = tuple(filter_val) + + def final_filter(record: dict[str, Any]) -> bool: + return bool(record["name"] and record["name"].startswith(prefixes)) + + elif isinstance(filter_val, str): + + def final_filter(record: dict[str, Any]) -> bool: + return bool(record["name"] and record["name"].startswith(filter_val)) + + else: + final_filter = None if filter_val is False else filter_val # type: ignore[assignment] + + _HANDLER_ID = logger.add( # ty: ignore[no-matching-overload] + settings.sink, # type: ignore[arg-type] + level=settings.level, + filter=final_filter, # type: ignore[arg-type] + format=log_format, # type: ignore[arg-type] + enqueue=settings.enqueue, + **settings.kwargs, + ) + +## File: src/rustarium/settings.py + +""" +Settings configuration for the rustarium application. + +This module provides the primary configuration structure for the application +using Pydantic Settings. It aggregates configuration from multiple sources, +including environment variables and CLI arguments, and exposes a unified interface +for safe, typed configuration access across the codebase. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import ClassVar, Literal + +from pydantic import Field +from pydantic_settings import ( + BaseSettings, + SettingsConfigDict, +) -## Repository Files -- [README.md](https://github.com/markurtz/rustarium/blob/main/README.md): Project overview and quick start -- [AGENTS.md](https://github.com/markurtz/rustarium/blob/main/AGENTS.md): AI agent coding instructions -- [DEVELOPING.md](https://github.com/markurtz/rustarium/blob/main/DEVELOPING.md): Developer setup guide +__all__ = ["Settings"] -## Optional -- [Full Documentation](https://markurtz.github.io/rustarium/llms-full.txt): The complete concatenated documentation for rustarium +class Settings(BaseSettings): + """ + Configuration state for the application. + + This class aggregates and prioritizes configuration from multiple sources, + providing a unified state for the application. It is built on top of + pydantic-settings to allow validation, default values, and type coercion. + + Example: + .. code-block:: python + + from rustarium.settings import Settings + + settings = Settings(environment="production") + print(settings.project_root) + """ + + model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict( + arbitrary_types_allowed=True, + extra="ignore", + populate_by_name=True, + validate_assignment=True, + env_prefix="RUSTARIUM__", + ) + """Pydantic config dict dictating environment prefixes and validation.""" + + # Core Application Properties + project_root: Path = Field( + default_factory=Path.cwd, + description=( + "The root directory of the project. Used for resolving relative paths." + ), + ) + environment: Literal["development", "staging", "production"] = Field( + default="development", + description="The current deployment environment of the application.", + ) + + def __str__(self) -> str: + """ + Return a concise string representation of the settings. + + :return: A concise, human-readable string summary of the settings. + :rtype: str + """ + return ( + f"Settings(environment={self.environment!r}, " + f"project_root={self.project_root!r})" + ) + + def __repr__(self) -> str: + """ + Return a detailed string representation of the settings. + + :return: A detailed string representation suitable for debugging. + :rtype: str + """ + return ( + f"Settings(" + f"environment={self.environment!r}, " + f"project_root={self.project_root!r}" + f")" + ) + +## File: src/rustarium/client.py + +""" +Primary SDK clients for interacting with the Rustarium orchestration engine. +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator, Generator, Iterable +from typing import Annotated, Any, cast + +from pydantic_core import to_json + +from rustarium.schemas import JobConfig, UpdatePayload + +__all__ = [ + "AsyncRustariumClient", + "RustariumClient", +] + + +class AsyncRustariumClient: + """ + Asynchronous client for interacting with the Rustarium orchestration daemon. + """ + + def __init__(self) -> None: + """ + Initialize the asynchronous client. + + TODO: Implement PyO3 bridging and Rust daemon initialization. + """ + + async def submit( + self, + workloads: Annotated[ # noqa: ARG002 + Iterable[Any], + "The iterable of workloads or data payloads to process.", + ], + default_config: Annotated[ + JobConfig | None, + "Default configuration for the batch.", + ] = None, + ) -> AsyncGenerator[UpdatePayload, None]: + """ + Submit a batch of workloads to the orchestration engine. + + TODO: + - Serialize configuration using `pydantic_core.to_json()` + - Start the PyO3 async generator to drive the Rust/Tokio engine. + - Yield `UpdatePayload` instances parsed from the UDS stream. + """ + # Example to ensure pydantic_core usage is documented: + if default_config is not None: + _serialized_config = to_json(default_config, by_alias=True) # noqa: F841 + + # Stubbed yield to satisfy the generator signature + yield cast("UpdatePayload", NotImplemented) + + +class RustariumClient: + """ + Synchronous client for interacting with the Rustarium orchestration daemon. + """ + + def __init__(self) -> None: + """ + Initialize the synchronous client. + + TODO: Set up background worker thread insulation. + """ + self._async_client = AsyncRustariumClient() + + def submit( + self, + workloads: Annotated[ # noqa: ARG002 + Iterable[Any], + "The iterable of workloads or data payloads to process.", + ], + default_config: Annotated[ # noqa: ARG002 + JobConfig | None, + "Default configuration for the batch.", + ] = None, + ) -> Generator[UpdatePayload, None, None]: + """ + Submit a batch of workloads synchronously. + + This method wraps the core async execution pathway, offloading iteration to a + background thread to insulate the GIL. + + TODO: + - Implement `loop.run_in_executor()` logic to handle the sync generator. + - Bridge the async generator yields back to this blocking generator. + """ + # Stubbed yield to satisfy the generator signature + yield cast("UpdatePayload", NotImplemented) + +## File: src/rustarium/exceptions.py + +""" +Core exception hierarchy for the rustarium package. +""" + +from __future__ import annotations + +__all__ = [ + "ConfigurationError", + "OrchestrationError", + "RustariumError", + "SecurityError", + "WorkerError", +] + + +class RustariumError(Exception): + """ + Base exception class for all Rustarium errors. + """ + + +class ConfigurationError(RustariumError): + """ + Exception raised for invalid constraints, unparseable objects, or bad + environment state. + """ + + +class OrchestrationError(RustariumError): + """ + Exception raised for general engine failures, communication timeouts, + or scheduling issues. + """ + + +class WorkerError(RustariumError): + """ + Exception raised for process crashes, user-code exceptions, or + application-level panics. + """ + + +class SecurityError(RustariumError): + """ + Exception raised for isolation violations such as attempting a SandboxEscape, + or breaching ceilings. + """ + +## File: src/rustarium/compat.py + +""" +Compatibility abstractions for optional dependencies. + +This module centralizes fallback logic for safely importing optional dependencies +like ``opentelemetry``. It provides standardized access points for these modules, +avoiding scattered ``try-except`` blocks across the codebase. Maintainers should +import optional dependencies from this module rather than attempting direct +imports elsewhere. +""" + +from __future__ import annotations + +import types +from typing import Annotated + +_opentelemetry_trace: types.ModuleType | None +try: + from opentelemetry import ( # type: ignore[import-not-found, unused-ignore] + trace as _opentelemetry_trace_mod, + ) + + _opentelemetry_trace = _opentelemetry_trace_mod +except ImportError: + _opentelemetry_trace = None + +__all__ = ["opentelemetry_trace"] + +opentelemetry_trace: Annotated[ + types.ModuleType | None, + "Enables distributed tracing integration. Used for tracing execution paths " + "when OpenTelemetry is present. Provides the ``opentelemetry.trace`` module " + "or ``None``.", +] = _opentelemetry_trace + +## File: src/rustarium/__main__.py + +""" +Main entrypoint for the rustarium package. + +This module provides the executable routine when the package is run directly +via the command line (e.g., ``python -m rustarium``). It uses Typer to define +the CLI application and commands. +""" + +from __future__ import annotations + +from typing import Annotated + +import typer + +from rustarium.logging import LoggingSettings, configure_logger, logger +from rustarium.settings import Settings +from rustarium.version import __version__ + +__all__ = ["main"] + +app = typer.Typer( + help="Rustarium: High-performance process orchestrator and sandbox.", + context_settings={"help_option_names": ["-h", "--help"]}, +) + + +def _version_callback(value: bool) -> None: + """Callback to display the current version.""" + if value: + typer.echo(f"rustarium v{__version__}") + raise typer.Exit + + +@app.callback(invoke_without_command=True) +def _main_callback( + ctx: typer.Context, + version: Annotated[ # noqa: ARG001 + bool | None, + typer.Option( + "--version", + callback=_version_callback, + is_eager=True, + help="Show the application version and exit.", + ), + ] = None, +) -> None: + """ + Global setup for the CLI application. + Initializes application settings and logging. + """ + configure_logger( + LoggingSettings( + enabled=True, + level="INFO", + clear_loggers=True, + filter=("rustarium", "__main__"), + ) + ) + settings = Settings() + + if ctx.invoked_subcommand is None: + logger.info("Hello from rustarium v{}!", __version__) + logger.info("Settings: {}", settings) + + +@app.command() +def diagnose() -> None: + """ + Diagnose the system environment requirements. + + Automated checks for OS support, cgroups v2 availability, Docker socket permissions, + and Python virtual environment state. + + TODO: Implement diagnostic checks. + """ + logger.info("Running Rustarium diagnostics...") + typer.echo("Diagnostic logic not yet implemented. (TODO)") + + +@app.command() +def setup() -> None: + """ + Guide the user through configuring the local environment. + + TODO: Implement setup wizard. + """ + logger.info("Running Rustarium setup...") + typer.echo("Setup logic not yet implemented. (TODO)") + + +def main() -> None: + """ + Execute the main routine via Typer. + """ + app() + + +if __name__ == "__main__": + main() + +## File: src/rustarium/version.py + +""" +Auto-generated version file from git-versioned +""" + +from __future__ import annotations + +from typing import NamedTuple + +__all__ = [ + "__BUILD_METADATA__", + "__GIT_METADATA__", + "__VERSION_METADATA__", + "BuildMetadata", + "GitMetadata", + "VersionMetadata", + "__version__", + "version", +] + + +class VersionMetadata(NamedTuple): + major: int + minor: int + patch: int + pre: tuple[str, int] | None + post: int | None + dev: int | None + local: str | None + + +class GitMetadata(NamedTuple): + hash: str + branch: str + tag: str + dirty: list[str] + commit_count: int + commit_message: str + distance_from_head: int + is_head_commit: bool + + +class BuildMetadata(NamedTuple): + timestamp: str + host: str + python_version: str + id: str + + +__version__ = "0.0.2.dev3+68da829" +version = __version__ + +__VERSION_METADATA__ = VersionMetadata( + major=0, + minor=0, + patch=1, + pre=None, + post=None, + dev=20260514, + local='68da829', +) + +__GIT_METADATA__ = GitMetadata( + hash="68da829494c38d6b95e5dde9ff2eb2f593f27120", + branch="", + tag="v0.0.1", + dirty=['.devcontainer/Dockerfile', '.devcontainer/devcontainer.json', '.editorconfig', '.env.example', '.github/CODEOWNERS', '.github/actions/oci/build/action.yml', '.github/actions/oci/quality/action.yml', '.github/actions/oci/security/action.yml', '.github/actions/oci/tests/action.yml', '.github/actions/project/docs/action.yml', '.github/actions/project/quality/action.yml', '.github/actions/project/security/action.yml', '.github/actions/project/tests/action.yml', '.github/actions/python/quality/action.yml', '.github/actions/python/security/action.yml', '.github/actions/python/tests/action.yml', '.github/actions/rust/quality/action.yml', '.github/actions/rust/security/action.yml', '.github/actions/rust/tests/action.yml', '.github/workflows/_build_container.yml', '.github/workflows/_build_package.yml', '.github/workflows/_docs.yml', '.github/workflows/_link-check.yml', '.github/workflows/_quality.yml', '.github/workflows/_security.yml', '.github/workflows/_tests.yml', '.github/workflows/development.yml', '.github/workflows/main.yml', '.github/workflows/nightly.yml', '.github/workflows/pipeline-development.yml', '.github/workflows/pipeline-main.yml', '.github/workflows/pipeline-nightly.yml', '.github/workflows/pipeline-release.yml', '.github/workflows/pipeline-weekly.yml', '.github/workflows/release.yml', '.github/workflows/development_cleanup.yml -> .github/workflows/util-development-cleanup.yml', '.github/workflows/_pr_comment.yml -> .github/workflows/util-pr-comment.yml', '.github/workflows/weekly.yml', '.gitignore', '.pre-commit-config.yaml', '.yamllint', 'AGENTS.md', 'Cargo.lock', 'Cargo.toml', 'DEVELOPING.md', 'Dockerfile', 'PRD.md', 'README.md', 'crates/rustarium-core/Cargo.toml', 'crates/rustarium-core/src/lib.rs', 'crates/rustarium-core/src/sum.rs', 'crates/rustarium-core/tests/test_integration.rs', 'cst.yaml', 'docker-compose.yml', 'docs/getting-started/quickstart.md', 'docs/guides/github-workflows.md', 'docs/guides/index.md', 'docs/guides/repository-setup.md', 'docs/reference/index.md', 'docs/scripts/extra.js', 'docs/scripts/gen_ref_pages.py', 'docs/stylesheets/extra.css', 'llms-full.txt', 'mkdocs.yml', 'pyproject.toml', 'scripts/build_backend.py', 'scripts/build_docs.py', 'scripts/update_version.py', 'src/rustarium/__init__.py', 'src/rustarium/__main__.py', 'src/rustarium/_rust.pyi', 'src/rustarium/client.py', 'src/rustarium/compat.py', 'src/rustarium/exceptions.py', 'src/rustarium/logging.py', 'src/rustarium/schemas/__init__.py', 'src/rustarium/schemas/config.py', 'src/rustarium/schemas/updates.py', 'taplo.toml', 'tests/README.md', 'tests/integration/test_integration.py', 'tests/unit/test_init.py', 'tests/unit/test_settings.py', 'uv.lock', 'zensical.toml', '.secrets.baseline'], + commit_count=2, + commit_message="", + distance_from_head=0, + is_head_commit=False, +) + +__BUILD_METADATA__ = BuildMetadata( + timestamp="2026-05-22 18:35:08.455768+00:00", + host="Marks-MacBook-Pro-2.local", + python_version="3.13.13", + id="70823286-201c-40a9-8d4d-c292bbdbda25", +) + +## File: src/rustarium/_rust.pyi + +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Type stubs for the Rust binary extension module. +""" + +def sum_as_string(a: int, b: int) -> str: + """Sum two integers and return the result as a string.""" + +## File: Cargo.toml + +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.package] +version = "0.0.1-dev3+68da829" +edition = "2021" +authors = ["Mark Kurtz"] +license = "Apache-2.0" +repository = "https://github.com/markurtz/rustarium" + +[workspace.dependencies] +serde_json = "1.0" +bincode = "1.3" + +[workspace.dependencies.pyo3] +version = "0.24.1" +features = ["abi3-py310"] + +[workspace.dependencies.tokio] +version = "1.38" +features = ["full"] + +[workspace.dependencies.serde] +version = "1.0" +features = ["derive"] + +[workspace.metadata.bin.cargo-audit] +version = "0.22.1" +locked = true + +[workspace.metadata.bin.cargo-deny] +version = "0.19.6" +locked = true + +[profile.dev] +split-debuginfo = "unpacked" + +[profile.dev.package."*"] +opt-level = 3 + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 + +## File: crates/rustarium-core/Cargo.toml + +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[package] +name = "rustarium-core" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +description = "Core Rust process orchestrator and sandbox library for Python and WASM." + +[lib] +name = "rustarium_core" +crate-type = ["cdylib", "rlib"] + +[dependencies] +pyo3 = { workspace = true } +tokio = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +bincode = { workspace = true } + +[features] +extension-module = ["pyo3/extension-module"] +default = [] + + + +## File: crates/rustarium-core/src/lib.rs + +// Copyright 2026 markurtz +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Core Python bindings for rustarium. + +use pyo3::prelude::*; + +pub mod sum; +pub use sum::sum_as_string_internal; + +/// Sums two integers and returns the result as a string. +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult { + sum::sum_as_string_internal(a, b) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + Ok(()) +} + +## File: crates/rustarium-core/src/sum.rs + +// Copyright 2026 markurtz +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Example module implementing sum logic and unit tests. + +use pyo3::prelude::*; + +/// Sums two integers and returns the result as a string. +pub fn sum_as_string_internal(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sum_as_string_internal() { + let res = sum_as_string_internal(3, 4).unwrap(); + assert_eq!(res, "7"); + } +} ## File: README.md + +

@@ -161,71 +1887,177 @@ If you use this repository or the resulting software in your research, please ci ## File: AGENTS.md + + # AGENTS.md — AI Agent & Coding Assistant Guide -> **This file provides repository-specific instructions to AI coding agents** (e.g., OpenAI Codex, GitHub Copilot, Gemini, Claude, Cursor). -> Human contributors should refer to [DEVELOPING.md](DEVELOPING.md). +This file provides repository-specific context, setup instructions, executable commands, and security boundaries for AI coding assistants. -## Project Context +## System Overview -**`rustarium`** A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust. -**Primary language:** `Python 3.10+`\ -**Package manager:** `Hatch` +`rustarium` is a high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust. -## Critical Constraints +- **Primary Languages:** Python 3.10+ / Rust +- **Configuration & Build Backend:** Hatch (with Maturin backend for native Rust extension compilation) +- **Key Dependencies:** PyO3, Loguru, Pydantic / Pydantic-Settings v2, Typer, Opentelemetry -> [!CAUTION] -> -> 1. **Never commit secrets.** Do not add API keys, tokens, or credentials anywhere. -> 1. **Do not modify `LICENSE` or `NOTICE`.** These are legally binding. -> 1. **Do not modify workflow trigger conditions** without human review. -> 1. **All new source files must include the Apache 2.0 copyright header.** -> 1. **Use `{{double_braces}}` for template placeholders.** Never hard-code them. +## Core Directories & Architecture -## Repository Layout +- `src/rustarium/`: Python interface and orchestrator client. + - `__main__.py`: CLI entrypoint (subcommands: `diagnose`, `setup`). + - `settings.py`: Pydantic settings schema for project options. + - `client.py`: Process client wrapper. + - `logging.py`: Loguru logging and telemetry hooks. +- `crates/rustarium-core/`: Core Rust library compiling PyO3 bindings (`rustarium._rust`). + - `src/lib.rs`: Rust module bindings and PyO3 setup. + - `src/sum.rs`: Sandboxed computation functions. +- `tests/`: Organized into `python/unit/` (isolated logic), `python/integration/` (subsystem interactions), and `e2e/` (orchestrator black-box integration). +- `docs/`: MkDocs Material documentation source using Zensical. +- `.github/workflows/`: CI/CD workflows. -- `.github/workflows/`: CI/CD pipelines. Files prefixed with `_` are reusable templates. -- `docs/`: Zensical documentation source. -- `src/`: Primary application source code. -- `tests/`: Organized into `python/unit/`, `python/integration/`, and `e2e/`. +## Environment & Developer Workflows -## Executable Commands +This project is configured to run using Hatch environments. Use the local `.venv` for all executions as instructed by the user. -- **Formatting:** `hatch run all:format` (or `hatch run python:format`, `hatch run rust:format`, `hatch run project:format`, `hatch run oci:format`) -- **Linting:** `hatch run all:lint` (cascades to all environments) or individual checks (e.g., `hatch run python:lint`, `hatch run project:lint`) -- **Type Checking:** `hatch run all:types` (cascades to python/rust check targets) or environment-specific -- **Testing:** `hatch run all:tests` (runs python + rust unit/integration and project e2e), `hatch run python:tests` (with optional `-cov` suffix) -- **Security Audits:** `hatch run all:security` or environment-specific (e.g., `hatch run python:security`) -- **Docs:** `hatch run project:serve-docs` (local dev server) / `hatch run project:build-docs` (static build using Zensical) -- **Build:** `hatch build` (Maturin Python package build) or `hatch run all:build` (builds python, OCI, docs) +### 1. Setup & Bootstrapping -## Code Style & Patterns +Activate the environment and initialize Hatch: -- **No magic strings or numbers** — define constants. -- **Prefer explicit over implicit.** -- **One responsibility per module.** -- Every public function, class, and module must have a docstring. -- Follow [Conventional Commits](https://www.conventionalcommits.org/). +```bash +# Set up/update dependencies via Hatch inside virtualenv wrapper +.venv/bin/hatch env create +``` + +### 2. Testing Pipeline + +Tests are tiered across languages. Run targeted tests or full suite: + +```bash +# Run all Python functional tests (unit + integration) +.venv/bin/hatch run python:tests-func + +# Run Python unit tests only +.venv/bin/hatch run python:tests-unit + +# Run Python integration tests only +.venv/bin/hatch run python:tests-int + +# Run Rust unit and integration tests +.venv/bin/hatch run rust:tests + +# Run OCI Container Structure Tests (CST) +.venv/bin/hatch run oci:tests + +# Run E2E tests (builds dist wheel and installs it first) +.venv/bin/hatch run project:tests-e2e + +# Run all tests with coverage reports +.venv/bin/hatch run tests-cov +``` + +### 3. Code Quality, Formatting & Types + +Run formatting and quality gates before committing: + +```bash +# Auto-format Python, Rust, and project configuration files +.venv/bin/hatch run python:format +.venv/bin/hatch run rust:format +.venv/bin/hatch run project:format + +# Run lint checks (Ruff, Clippy, mdformat, yamlfix, taplo) +.venv/bin/hatch run python:lint +.venv/bin/hatch run rust:lint +.venv/bin/hatch run project:lint + +# Run static type checks (Mypy via Ty for Python, cargo check for Rust) +.venv/bin/hatch run python:types +.venv/bin/hatch run rust:types +``` -## GitHub Actions Workflows +### 4. Documentation & Packaging -- **Reusable Templates (`_*.yml`):** Never trigger directly. Ensure changes are backward-compatible. -- **Lifecycle:** - - `development.yml`: PR open/sync (unit + smoke) - - `main.yml`: Push to `main` (unit + integration + sanity) - - `nightly.yml`, `weekly.yml`, `release.yml`: Standard scheduled/release flows. +```bash +# Build and serve docs locally (http://127.0.0.1:8000) +.venv/bin/hatch run project:docs-serve + +# Build package distributions (sdist and wheel compiling Rust bindings) +.venv/bin/hatch build +``` + +## Security & Behavior Boundaries + +To maintain project integrity and security, agents must strictly adhere to the following rules: + +### 1. Secrets & Credentials + +- **Never commit secrets:** Never add API keys, tokens, or credentials anywhere. +- Run security audits using: `.venv/bin/hatch run project:security`. + +### 2. Critical Files & CI Guardrails + +- **Do not modify `LICENSE` or `NOTICE`.** +- **Do not modify GitHub Actions workflow triggers or steps** (in `.github/`) without explicit human review. +- **Apache 2.0 copyright header:** Every new source file (Python or Rust) must begin with the standard Apache 2.0 copyright and license notice. -## Documentation +### 3. Execution Constraints -- **`docs/`** and **`zensical.toml`** control the site. -- Use `{{placeholder}}` variables for templated fields (e.g., `rustarium`, `markurtz`). +- Always use tools installed in the `.venv` (e.g. `.venv/bin/hatch`, `.venv/bin/pytest`). +- Avoid global packages or running unverified external binaries. +- Do not add new external dependencies to `pyproject.toml` without verifying compatibility with Python 3.10+. -## Agent Notes +## File: CLAUDE.md -_Add notes here when updating instructions for AI agents._ + + +Refer to [AGENTS.md](AGENTS.md) for build, testing, quality controls, and agent instructions. ## File: DEVELOPING.md + + # Developing `rustarium` This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards. @@ -241,276 +2073,379 @@ Ensure your system meets the requirements below to establish a consistent local ### Development Environment Container (.devcontainer) -- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **Dev Containers** extension installed. +- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **[Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers)** extension installed. - **Usage**: 1. Clone this repository: `git clone https://github.com/markurtz/rustarium.git` 1. Open the project folder in VS Code. 1. A prompt will appear: "Reopen in Container". Click it to launch the environment. 1. VS Code will build the container, install the stable Rust toolchain, and automatically run `uv sync --all-groups --all-extras` to install and sync the Python environment. +> [!NOTE] +> **Local `.venv` vs. Hatch Environments**: +> The `uv sync` command creates a local `.venv` in the project root solely to provide VS Code extensions (like [Pylance](https://github.com/microsoft/pylance-release) and [Ruff](https://astral.sh/ruff)) with a standard environment for editor autocomplete, hover information, and in-editor diagnostics. All command-line and automated task execution (formatting, linting, testing, building) is managed via **[Hatch](https://hatch.pypa.io/)** isolated environments (`hatch run ...`). Do not activate or modify this root `.venv` directly for running tasks. + ### Local Setup - **[Git](https://git-scm.com/)**: Version control tool. Refer to the [Git Documentation](https://git-scm.com/doc) for installation instructions. - **[Docker](https://www.docker.com/)**: Container management system. Install via the [Docker Installation Guide](https://docs.docker.com/get-docker/). +- **System-level Build Tools**: Compiling native Rust extensions for Python (via `Maturin`) requires a C compiler and development headers: + - **macOS**: Install Xcode Command Line Tools by running `xcode-select --install`. + - **Linux (Debian/Ubuntu)**: Install `build-essential`, `clang`, `pkg-config`, and `libssl-dev`. + - **Linux (Fedora/RHEL)**: Install `development-tools`, `clang`, `pkg-config`, and `openssl-devel`. - **[Python](https://www.python.org/) 3.10 - 3.14**: Core runtime environment. Install via the [Python Downloads Page](https://www.python.org/downloads/). - **[Rust & rustup](https://rustup.rs/)**: Stable compiler toolchain. Install via the [Rustup Installer](https://rustup.rs/). - **[uv](https://docs.astral.sh/uv/)**: Fast package installer and resolver. Install via the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/). -- **[Hatch](https://hatch.pypa.io/)**: Project workflow orchestrator. Install via the [Hatch installation guide](https://hatch.pypa.io/latest/install/). +- **[Hatch](https://hatch.pypa.io/)**: Project workflow orchestrator. Install via the [Hatch installation guide](https://hatch.pypa.io/latest/install/). If you have `uv` installed, we recommend installing Hatch cleanly as a tool using: + ```bash + uv tool install hatch + ``` + to avoid polluting your global system packages. +- **Rust Dev Tools**: Local security audit tools (`cargo-audit` and `cargo-deny`) are managed via `cargo-run-bin`. The environment bootstraps `cargo-run-bin` automatically on environment creation/update via Hatch's `post-install-commands`. When you run a task in the `rust` environment (such as `hatch run rust:security`), the correct tool versions (locked in the root workspace `Cargo.toml`) will be automatically compiled and cached in the local `.bin/` folder. You can also explicitly trigger the bootstrap command using: + ```bash + hatch run rust:install-tools + ``` + +> [!TIP] +> **Editor Autocomplete Setup (Local)**: +> For local development outside of the Dev Container, if you want your editor (VS Code, [PyCharm](https://www.jetbrains.com/pycharm/), etc.) to resolve imports and provide autocomplete/diagnostics, run `uv sync --all-groups --all-extras` once to create the local `.venv`. ## Developer Quickstart -Once your environment is set up (either via the Dev Container or manually), follow this self-contained workflow for a complete end-to-end development cycle: +Once your environment is set up (either via the Dev Container or manually), follow this consolidated workflow for a standard development cycle: + +- **Branch & Code**: Create your feature branch and make changes: + ```bash + git checkout -b feat/my-contribution + ``` +- **Quality Assurance (Unified)**: Automatically format code, lint, type check, and run security scans across all environments: + ```bash + hatch run all:quality + ``` + *(Alternatively, you can run individual checks if preferred: `hatch run all:format`, `hatch run all:lint`, `hatch run all:types`, or `hatch run all:security`)* +- **Test (Unified)**: Run all unit, integration, and E2E tests with coverage: + ```bash + hatch run all:tests-cov + ``` + *(For running tests without coverage: `hatch run all:tests`)* +- **Build (Unified)**: Compile package artifacts (source & wheels) and build the OCI container image *(requires Docker daemon to be running for the OCI phase)*: + ```bash + hatch run all:build + ``` + *(To build only the Python wheel locally: `hatch build`)* +- **Serve Documentation**: Serve documentation locally (this automatically builds the site): + ```bash + hatch run all:docs-serve + ``` +- **Push**: Push your changes to open a Pull Request: + ```bash + git push -u origin feat/my-contribution + ``` + +## Hatch Development Environments Overview + +Our build, verification, and execution pipelines are partitioned into target-specific environments using Hatch. This ensures isolation, prevents dependency bloat, and standardizes workflows: + +- **`default`**: The base environment template. It configures shared environment variables (such as target paths, directory structures, and script file paths) and installs the core dependency groups. +- **`all`**: The orchestrator environment. It defines cascading workflows to run formatting, linting, typing, security scanning, testing, and documentation generation across all components sequentially or concurrently. +- **`python`**: Encompasses Python-specific verification tools including [Ruff](https://astral.sh/ruff) for linting/formatting, [Ty](https://github.com/astral-sh/ty) for type-checking, [Pytest](https://docs.pytest.org/) for testing, and [Typer](https://typer.tiangolo.com/) for CLI documentation generation. +- **`rust`**: Handles compiling, testing (`cargo test`), linting (`clippy`), type checking (`cargo check`), and API documentation generation (`cargo doc`) for the underlying Rust extension modules. +- **`oci`**: Manages OCI container builds (`docker build`), compose verification (`docker compose config`), linting ([hadolint](https://github.com/hadolint/hadolint)), security auditing ([trivy](https://trivy.dev/), [dockle](https://github.com/goodwithtech/dockle)), and container structure tests ([cstest](https://github.com/GoogleContainerTools/container-structure-test)). +- **`project`**: Targets repository-wide configuration and file standards, including Markdown formatting (`[mdformat](https://github.com/executablebooks/mdformat)`), configuration checkouts (`[yamlfix](https://github.com/lyz-code/yamlfix)`, `[yamllint](https://github.com/adrienverge/yamllint)`, `[taplo](https://taplo.tamasfe.dev/)`), security baselines (`[detect-secrets](https://github.com/Yelp/detect-secrets)`, `[checkov](https://www.checkov.io/)`), link checkers, and site compilation (using the **[Zensical](https://zensical.org)** static site generator/documentation compiler). -### 1. Branching & Changes +## Coding Workflows -Create a new feature branch and make your changes across Python, Rust, or OCI files: +All development commands are unified under [pyproject.toml](./pyproject.toml) and managed using Hatch. The commands are generally invoked using the format: ```bash -git checkout -b feat/my-contribution +hatch run [ENVIRONMENT]:[SCRIPT] ``` -### 2. Formatting & Linting - -Run all formatters and lint checks across the codebase to verify code quality and compliance: +For orchestrating tasks across all environments, use the `all` environment scripts: ```bash -# Auto-format codebase styles and rules -hatch run all:format # Cascades to format project, python, rust, and oci files - -# Run read-only quality gating validation -hatch run all:lint # Cascades to lint project, python, rust, and oci files +hatch run all:[SCRIPT] ``` -### 3. Static Type Analysis +### Quality Assurance & Static Analysis -Validate that types resolve correctly across languages: +This workflow enforces code quality, style conventions, static type correctness, and security policies across all codebase layers. -```bash -# Verify static types across Python and Rust -hatch run all:types -``` +> [!TIP] +> **Unified Quality Check**: +> You can run all formatting, linting, type-checking, and security scans across all environments in a single command using the unified quality check: +> +> ```bash +> hatch run all:quality +> ``` + +| Environment | Formatting Command | Linting Command | Type-Checking Command | Security Auditing Command | +| :--------------------- | :------------------------- | :----------------------- | :--------------------------- | :--------------------------- | +| **All / Orchestrator** | `hatch run all:format` | `hatch run all:lint` | `hatch run all:types` | `hatch run all:security` | +| **Python** | `hatch run python:format` | `hatch run python:lint` | `hatch run python:types` | `hatch run python:security` | +| **Rust** | `hatch run rust:format` | `hatch run rust:lint` | `hatch run rust:types` | `hatch run rust:security` | +| **OCI** | `hatch run oci:format` | `hatch run oci:lint` | `hatch run oci:types` \* | `hatch run oci:security` | +| **Project** | `hatch run project:format` | `hatch run project:lint` | `hatch run project:types` \* | `hatch run project:security` | + +*\* Note: Type checking is not applicable for OCI and Project environments; executing these commands will output an information message.* + +#### Code Formatting + +- **Tools / Methodology / Rationale**: + - **Python**: Uses `[ruff](https://astral.sh/ruff)` to automatically check/fix imports and format code layout. This delivers high-performance style standardization. + - **Rust**: Uses `cargo fmt` to enforce the official Rust layout style and `cargo clippy --fix` to safely resolve compiler styling suggestions. + - **OCI**: Uses `[dclint](https://github.com/zavoloklom/docker-compose-linter)` (via a helper script) to auto-format Docker Compose files. While `dclint` is primarily a compose linter, the format step (`hatch run oci:format`) executes it with the `--fix` flag to automatically correct lint errors and standard style issues in place. (Dockerfile linting/validation is handled separately by `[hadolint](https://github.com/hadolint/hadolint)`). + - **Project**: Employs `[mdformat](https://github.com/executablebooks/mdformat)` for Markdown, `[yamlfix](https://github.com/lyz-code/yamlfix)` for YAML files, and `[taplo](https://taplo.tamasfe.dev/)` for TOML file formatting to maintain a uniform structure for all configuration and documentation files. +- **Expected Outputs & Locations**: + - In-place modifications applied directly to the files targeted by the respective environment variables: `PYTHON_TARGETS`, `RUST_MANIFEST`, `MDFORMAT_TARGETS` (Markdown targets), `YAML_TARGETS`, and `TOML_TARGETS`. + +#### Linting & Verification + +- **Tools / Methodology / Rationale**: + - **Python**: Runs `[ruff](https://astral.sh/ruff) check` and `[ruff](https://astral.sh/ruff) format --check` to verify compliance with PEP 8 and project style guidelines without modifying files. + - **Rust**: Executes `cargo clippy` and `cargo fmt --check` to run comprehensive static analysis lints, treating all warnings as errors (`-D warnings`). + - **OCI**: Uses `[hadolint](https://github.com/hadolint/hadolint)` to validate Dockerfile syntax and standard practices, and runs `docker compose config` to verify the syntactic and semantic validity of compose files. + - **Project**: Runs `[mdformat](https://github.com/executablebooks/mdformat) --check` to check Markdown formatting, `[yamlfix](https://github.com/lyz-code/yamlfix) --check` and `[yamllint](https://github.com/adrienverge/yamllint)` for YAML files, and `[taplo](https://taplo.tamasfe.dev/) check` for TOML configuration syntax. +- **Expected Outputs & Locations**: + - Summary reports, warnings, and errors output directly to the terminal stdout/stderr. Standard exit codes (non-zero on failures) are used to gate CI pipelines. + +#### Static Type Checking + +- **Tools / Methodology / Rationale**: + - **Python**: Employs Astral's `[ty check](https://github.com/astral-sh/ty)` frontend to statically analyze and verify Python type annotations. + - **Rust**: Executes `cargo check --all-targets` to quickly analyze the Rust codebase and verify compile-time type safety without generating binary artifacts. +- **Expected Outputs & Locations**: + - Type checker error listings and tracebacks are printed to the terminal console. -### 4. Unit & Integration Testing +#### Security & Vulnerability Auditing -Verify component-level functionality, correctness, and coverage: +- **Tools / Methodology / Rationale**: + - **Python**: Employs `[semgrep](https://semgrep.dev/)` for semantic pattern matching, `[pip-audit](https://github.com/pypa/pip-audit)` to detect known vulnerabilities in Python packages, and `[ruff](https://astral.sh/ruff) check --select S` to check for security vulnerabilities. + - **Rust**: Uses `cargo-run-bin` (`cargo bin cargo-audit` and `cargo bin cargo-deny`) to scan dependencies for CVEs reported in the Rust Sec Advisory Database, and to audit licenses and sources. The required versions of these tools are locked in the root workspace `Cargo.toml` and cached locally in `.bin/`. + - **OCI**: Scans built containers using `[dockle](https://github.com/goodwithtech/dockle)` (verifies image best practices/secrets) and `[trivy](https://trivy.dev/)` (scans OS-level packages for CVEs). + - **Project**: Employs `[detect-secrets](https://github.com/Yelp/detect-secrets)` to scan for accidentally committed secrets against a baseline, and `[checkov](https://www.checkov.io/)` to scan infrastructure-as-code files and development configurations. +- **Expected Outputs & Locations**: + - Standard reports output to the console. + - Project environment updates and validates the secrets baseline file located at `.detect-secrets.scan.json`. Run `hatch run project:security-update` to update this baseline file. + +### Testing Strategy & Suites + +Our testing strategy is split into component-level, integration-level, and system-level suites, each of which supports code coverage reporting. + +#### Coverage Configurations & Directories -```bash -# Python unit and integration tests with coverage tracking -hatch run python:tests-cov +Code coverage runs collect data during test executions and format them into human-readable Markdown summaries. -# Rust unit and integration tests -hatch run rust:tests +- **Python Coverage**: Configured to output to `coverage/python/` (`PYTHON_COV_DIR`). The test suites automatically output terminal reports and compile Markdown reports (e.g., `coverage_tests-unit.md`). +- **Rust Coverage**: Configured to output to `coverage/rust/` (`RUST_COV_DIR`). It utilizes `cargo llvm-cov` to collect coverage, outputs `.json` metrics, and validates them against coverage gates via `covgate`, exporting Markdown reports (e.g., `coverage_tests-unit.md`). -# OCI Container Structure Tests (CST) -hatch run oci:tests -``` +| Test Suite | Python Command | Rust Command | OCI Command | Project Command | All Command | +| :------------------------- | :-------------------------------- | :------------------------------ | :---------------------------- | :---------------------------------- | :----------------------------- | +| **All Local Tests** | N/A | N/A | N/A | N/A | `hatch run all:tests` | +| **All Tests + Coverage** | N/A | N/A | N/A | N/A | `hatch run all:tests-cov` | +| **Functional Tests** | `hatch run python:tests-func` | `hatch run rust:tests-func` | N/A | N/A | `hatch run all:tests-func` | +| **Func Tests + Coverage** | `hatch run python:tests-func-cov` | `hatch run rust:tests-func-cov` | N/A | N/A | `hatch run all:tests-func-cov` | +| **Unit Tests** | `hatch run python:tests-unit` | `hatch run rust:tests-unit` | N/A | N/A | `hatch run all:tests-unit` | +| **Unit Tests + Coverage** | `hatch run python:tests-unit-cov` | `hatch run rust:tests-unit-cov` | N/A | N/A | `hatch run all:tests-unit-cov` | +| **Integration Tests** | `hatch run python:tests-int` | `hatch run rust:tests-int` | N/A | `hatch run project:tests-int` | `hatch run all:tests-int` | +| **Int Tests + Coverage** | `hatch run python:tests-int-cov` | `hatch run rust:tests-int-cov` | N/A | `hatch run project:tests-int-cov` | `hatch run all:tests-int-cov` | +| **End-to-End Tests** | `hatch run python:tests-e2e` | N/A | `hatch run oci:tests-e2e` | `hatch run project:tests-e2e` | `hatch run all:tests-e2e` | +| **E2E Tests + Coverage** | `hatch run python:tests-e2e-cov` | N/A | `hatch run oci:tests-e2e-cov` | `hatch run project:tests-e2e-cov` | `hatch run all:tests-e2e-cov` | +| **Link Checks** | N/A | N/A | N/A | `hatch run project:link-checks` | N/A | +| **Link Checks + Coverage** | N/A | N/A | N/A | `hatch run project:link-checks-cov` | N/A | -### 5. System End-to-End (e2e) Testing +*\* Note: While Hatch commands for `N/A` cells can technically be run (and will print a message stating that the test suite is not defined for that environment), they have no logical test targets or execution paths. They are marked `N/A` for clarity.* -Run the complete system-level black-box integration test suite: +#### Test Suites Breakdown -```bash -hatch run project:tests-e2e -# Or run the entire test suite (unit + integration + e2e) -hatch run all:tests -``` +#### Full Suite (`tests` / `tests-cov`) -### 6. Local Compilation & Manual Verification +- **Methodology & Rationale**: Executes all local functional and E2E tests across all environments to ensure complete validation of the codebase before code integration. +- **Expected Outputs & Locations**: Unified console log output, combined test summaries, and all coverage Markdown files compiled under `coverage/python/` and `coverage/rust/`. -Build the projects and spin them up locally to verify they run cleanly: +#### Functional Testing (`tests-func` / `tests-func-cov`) -```bash -# Build Python wheels and compile Rust bindings via Maturin -hatch build - -# Build and run the local container orchestrator stack -hatch run oci:build # Builds the production image -docker compose up -d # Spins up orchestrator stack -docker compose ps # Verifies running services and health checks -docker compose down # Tears down the compose environment -``` +- **Methodology & Rationale**: Executes both unit and integration tests under the targeted environment to verify logical flows and subsystem communication. +- **Expected Outputs & Locations**: + - **Python**: Outputs to console and `coverage/python/coverage_tests-func.md`. + - **Rust**: Compiles coverage details into `coverage/rust/coverage_tests-func.json` and checks the gates to output `coverage/rust/coverage_tests-func.md`. + +#### Unit Testing (`tests-unit` / `tests-unit-cov`) + +- **Methodology & Rationale**: + - **Python**: Runs isolated tests under `tests/python/unit` via `pytest`. Focuses on validating individual modules and class behaviors. + - **Rust**: Runs isolated tests using `cargo test --lib --bins`. Validates pure internal Rust crate logic. +- **Expected Outputs & Locations**: + - **Python**: Outputs `coverage/python/coverage_tests-unit.md`. + - **Rust**: Outputs `coverage/rust/coverage_tests-unit.md` and `coverage/rust/coverage_tests-unit.json`. -### 7. Documentation Build Verification +#### Integration Testing (`tests-int` / `tests-int-cov`) -Ensure your documentation changes compile and render without errors: +- **Methodology & Rationale**: + - **Python**: Runs tests under `tests/python/integration` via `pytest` to verify interactions between Python modules. + - **Rust**: Runs integration test targets defined under the `tests/` directory of the Rust crate via `cargo test --test '*'` to verify cross-module/macro integration. + - **Project**: Runs documentation code block tests. It utilizes `scripts/generate_doc_tests.py` to parse Markdown files and compile code block assertions under `.tests/docs` (`DOC_TESTS_PATH`), which are then executed using `pytest`. +- **Expected Outputs & Locations**: + - **Python**: Outputs `coverage/python/coverage_tests-int.md`. + - **Rust**: Outputs `coverage/rust/coverage_tests-int.md` and `coverage/rust/coverage_tests-int.json`. + - **Project**: Verifies doc tests compile and pass; outputs progress to stdout. -```bash -hatch run project:serve-docs -``` +#### End-to-End Testing (`tests-e2e` / `tests-e2e-cov`) -Open `http://localhost:8000` to preview the rendered documentation site. +- **Methodology & Rationale**: + - **Python**: Compiles Python packages with `hatch build`, force reinstalls them via `pip`, and runs pytest against `tests/e2e` (`E2E_TESTS`) to verify CLI commands and package distribution paths in a black-box environment. + - **OCI**: Builds the OCI image and executes Google's Container Structure Tests (`cstest` via `scripts/run_oci.py`) to confirm that the image metadata, file layouts, and execution endpoints conform to specifications. + - **Project**: Runs automated tests across the `examples/` directory using pytest to verify real-world integrations. +- **Expected Outputs & Locations**: + - **Python**: Outputs `coverage/python/coverage_tests-e2e.md`. + - **OCI**: Outputs Container Structure Test results to the console. + - **Project**: Outputs example test execution summaries to the console. -### 8. Push & Pull Request +#### Link Checking (`link-checks` / `link-checks-cov`) -Verify formatting and lint check gates locally before pushing your changes: +- **Methodology & Rationale**: + - **Project**: Executes `scripts/check_links.py` to recursively crawl project documents (`MDFORMAT_TARGETS`) and verify all internal/external links resolve successfully. +- **Expected Outputs & Locations**: + - **Project**: Outputs link-checking validation summaries to the console. -```bash -hatch run all:lint -git push -u origin feat/my-contribution -``` +#### Test Categorization & Test Pathways -After pushing successfully, open a Pull Request (PR) on GitHub to trigger the automated CI check pipelines. +To manage test execution speed and pipeline efficiency, every Python test is categorized into one of our three test pathways: **smoke**, **sanity**, or **regression**. These pathways directly govern how frequently and in which environments those tests are executed in CI/CD pipelines. -## Coding Workflows +##### Pathway Specification & Filtering -Our codebase consists of Python modules, Rust crates, and Docker orchestration tools. Follow the specific guidelines below for each pathway. +A test's pathway can be specified and detected in one of two ways: -All development commands are unified under `pyproject.toml` and managed using Hatch in the format: +1. **By Marker**: Decorating the test function or class with a custom pytest marker (e.g., `@pytest.mark.smoke`, `@pytest.mark.sanity`, `@pytest.mark.regression`). +1. **By Name**: Including the pathway name in the test function or class name (e.g., `def test_smoke_initialization()`, `class TestSanityCore`, `def test_regression_bug_fix()`). -```bash -hatch run [ENVIRONMENT]:[COMMAND] -``` +##### Test Pathways Breakdown -Or you can use the global orchestrator targets: +- **`smoke`**: + - **Encapsulation & Scope**: Extremely fast, non-flaky, critical-path verification checks. These confirm that the fundamental, basic logic of the application functions correctly (e.g., orchestrator bootstrap, CLI command recognition). + - **Execution Frequency**: Run on **every commit and Pull Request** (e.g., `development.yml`) as a quick health gate. +- **`sanity`**: + - **Encapsulation & Scope**: Detailed, comprehensive tests of core system behaviors, APIs, and edge cases. These verify that the main business logic functions robustly but may take slightly longer than smoke tests. + - **Execution Frequency**: Run on **pushes to the main branch** (e.g., `main.yml`) and release branches to ensure overall stability of the codebase. +- **`regression`**: + - **Encapsulation & Scope**: Deep, system-wide, and heavy integration/E2E regression verification checks. These ensure that complex interactions, edge cases, and past bugs do not reappear. + - **Execution Frequency**: Run on **nightly, weekly, and release schedule pipelines** due to their longer execution time. -```bash -hatch run all:[COMMAND] -``` +##### Default Pathway Execution -### Subsection 1: Quality Assurance & Analysis - -We enforce strict quality controls across the repository using automated formatting, linting, type-checking, and security scanners. - -#### 1. Code Formatting - -Auto-formats style rules across the codebase. - -- **Project level**: Formats Markdown, YAML, and TOML. - - *Tools*: `mdformat` (Markdown), `yamlfix` (YAML), `taplo` (TOML). - - *Command*: `hatch run project:format` -- **Python**: Applies import sorting and style changes. - - *Tools*: `ruff`. - - *Command*: `hatch run python:format` -- **Rust**: Enforces official Rust layout styles. - - *Tools*: `cargo fmt`. - - *Command*: `hatch run rust:format` -- **OCI**: Formats Compose and container specs. - - *Tools*: `dclint`. - - *Command*: `hatch run oci:format` -- **Global Orchestrator**: Runs all formatters sequentially. - - *Command*: `hatch run all:format` - -#### 2. Linting & Verification - -Performs read-only validation of codebase syntax and configurations. - -- **Project level**: Checks Markdown links, formatting, YAML rules, and TOML. - - *Tools*: `mdformat --check`, `yamlfix --check`, `yamllint`, `taplo check`. - - *Command*: `hatch run project:lint` -- **Python**: Validates standard PEP 8, unused imports, and style rules. - - *Tools*: `ruff check`. - - *Command*: `hatch run python:lint` -- **Rust**: Runs Clippy compiler lints as errors. - - *Tools*: `cargo clippy`. - - *Command*: `hatch run rust:lint` -- **OCI**: Validates Dockerfile syntax and compose files. - - *Tools*: `hadolint`, `docker compose config`. - - *Command*: `hatch run oci:lint` -- **Global Orchestrator**: Checks lint rules across all files. - - *Command*: `hatch run all:lint` - -#### 3. Static Type Checking - -Validates type signatures across languages. - -- **Python**: Standard type verification. - - *Tools*: Astral's `ty`. - - *Command*: `hatch run python:types` -- **Rust**: Compiles target crates to verify type safety. - - *Tools*: `cargo check`. - - *Command*: `hatch run rust:types` -- **Global Orchestrator**: Verifies Python and Rust types. - - *Command*: `hatch run all:types` - -#### 4. Security & Vulnerability Auditing - -Scans for secrets, bad dependencies, and insecure patterns. - -- **Project level**: Detects hardcoded secrets, checks IaC compliance. - - *Tools*: `detect-secrets`, `checkov`, `guarddog`. - - *Command*: `hatch run project:security` -- **Python**: Audits dependencies and code rules. - - *Tools*: `semgrep`, `pip-audit`, `ruff` security rules. - - *Command*: `hatch run python:security` -- **Rust**: Validates dependency trees for CVEs and licenses. - - *Tools*: `cargo audit`, `cargo deny`. - - *Command*: `hatch run rust:security` -- **OCI**: Scans built containers for CVEs. - - *Tools*: `trivy`, `dockle`. - - *Command*: `hatch run oci:security` -- **Global Orchestrator**: Executes security sweeps across the stack. - - *Command*: `hatch run all:security` +If no specific filtering arguments, markers, or keyword flags are provided, pytest assumes a **regression** pathway by default. -______________________________________________________________________ +Running the test suite without any arguments executes a full regression run. This is because a default regression run executes: -### Subsection 2: Testing Strategy +- All smoke tests +- All sanity tests +- All regression tests +- Any tests not marked or named under a specific category -We separate testing into distinct scopes (unit, integration, and end-to-end) across different environment runtimes. +##### Filtering Python Tests -#### 1. Unit & Integration Testing +Hatch dynamically passes CLI arguments through to the underlying `pytest` execution via the `{args}` placeholder configured in [pyproject.toml](./pyproject.toml). To filter by test pathways (`smoke`, `sanity`, or `regression`), use pytest's keyword option (`-k`). This correctly matches both annotated markers and naming patterns (e.g., pathway keywords in the function or class name). -Verifies isolated functions and component-level communications. +- **Run only smoke tests**: + ```bash + hatch run python:tests-unit -k smoke + ``` +- **Run sanity and smoke tests**: + ```bash + hatch run python:tests-unit -k "sanity or smoke" + ``` -- **Python**: - - *Suite command*: `hatch run python:tests` - - *Suite with coverage*: `hatch run python:tests-cov` (generates HTML report under `docs/coverage/all/htmlcov`) - - *Unit-only*: `hatch run python:tests-unit` (coverage report generated under `docs/coverage/unit/htmlcov` via `hatch run python:tests-unit-cov`) - - *Integration-only*: `hatch run python:tests-int` (coverage report generated under `docs/coverage/integration/htmlcov` via `hatch run python:tests-int-cov`) - - *Test Markers*: We utilize `@pytest.mark.smoke` (smoke tests), `@pytest.mark.sanity` (standard sanity checks), and `@pytest.mark.regression` (regression verification). Filter tests via `-m` (e.g. `hatch run python:tests -m "smoke"`). - > [!CAUTION] - > **Pytest Custom Markers Caution**: - > Any new custom markers must be explicitly registered in `[tool.pytest.ini_options]` of `pyproject.toml`. Unregistered markers will trigger warnings and fail CI/CD checks. -- **Rust**: - - *Suite command*: `hatch run rust:tests` - - *Suite with coverage*: `hatch run rust:tests-cov` (runs `cargo llvm-cov`) - - *Unit-only*: `hatch run rust:tests-unit` (checks `lib` and `bins`) - - *Integration-only*: `hatch run rust:tests-int` (checks `tests/` directories) -- **OCI**: - - *Suite command*: `hatch run oci:tests` - - *Tools*: `container-structure-test` (verifies file placement, configuration settings, and executable commands in built images). -- **Global Orchestrator**: Runs code tests across both Python and Rust: - - *Command*: `hatch run all:tests-code` (with optional `-cov` suffix: `hatch run all:tests-code-cov`) +##### Testing a Specific Sub-Package or File -#### 2. End-to-End (e2e) Testing +Hatch environments make it easy to target a specific test directory, sub-package, or single file by appending the path to your `hatch run` command. The provided path will override the default directories configured in `pyproject.toml`. -Simulates real black-box orchestrator workflows in isolated system paths. +- **Run all tests in a specific file**: + ```bash + hatch run python:tests-unit tests/python/unit/test_settings.py + ``` +- **Run tests in a specific sub-package / directory**: + ```bash + hatch run python:tests-unit tests/python/unit/compat/ + ``` +- **Run functional tests for a specific integration file**: + ```bash + hatch run python:tests-func tests/python/integration/test_utils.py + ``` -- **Project level**: Runs pytest against the E2E directory to confirm whole-system compliance. - - *Command*: `hatch run project:tests-e2e` -- **Global Orchestrator**: Runs e2e suite through the orchestrator. - - *Command*: `hatch run all:tests-e2e` +##### Rust Test Categories -______________________________________________________________________ +For Rust tests, Cargo does not have native marker annotations. We achieve the same test scheduling by filtering tests based on name substrings. When invoking the Rust test suite in CI/CD via the `.github/actions/rust/tests` action: -### Subsection 3: Build & Distribution Pipelines +- `test-category: smoke` maps to running `cargo test -- smoke`, targeting tests with `smoke` in their name (e.g., `fn test_smoke_orchestrator()`). +- `test-category: sanity` maps to running `cargo test -- sanity`. +- `test-category: regression` maps to running `cargo test -- regression`. -These pipelines compile local binaries, generate distribution packages, and compile our developer documentation. +Always ensure that your Rust tests are named with one of these substrings if they fall under a specific category so they are correctly picked up by CI schedules. -#### 1. Compilation & Package Build +> [!WARNING] +> **Rust Test Substring Matching Danger**: +> Cargo's substring filter matches *any part* of a test's name. This carries a collision risk: for example, a test named `test_heavy_regression_smoke_system` will match `smoke` and execute in the smoke test pipeline. +> To prevent accidental execution of heavy tests in quick pipelines: +> +> 1. Use distinct, unambiguous suffixes or prefixes for tests (e.g. `_smoke`, `_sanity`, `_regression`) and avoid mixing these keywords. +> 1. For larger test suites, isolate tests into separate integration test binaries under the `tests/` directory (e.g. `tests/smoke.rs`, `tests/sanity.rs`, `tests/regression.rs`) and run them directly (e.g. `cargo test --test smoke`) to achieve strict isolation. -- **Python / Rust Bindings**: Compiles the underlying Rust extension modules and packages Python distribution wheels. - - *Tools*: `Maturin` and `hatchling` (configured under `[build-system]` and `[tool.maturin]` in `pyproject.toml`). - - *Command*: `hatch build`. -- **OCI Container Image**: Multi-stage Docker image builds. - - *Command*: `hatch run oci:build` (builds `rustarium:latest` injecting build date, git SHA, and version parameters). -- **Global Orchestrator**: Compiles and builds all package artifacts: - - *Command*: `hatch run all:build` (includes python packages, OCI image, and documentation). +### Documentation Workflows -#### 2. Documentation Build & Serving +Our documentation is managed as code. It includes auto-generated CLI references, compiled Rust API reference pages, and a unified project site built using **[Zensical](https://zensical.org)**. -We use **Zensical** to build and preview the project's developer guides, references, and release details. +> [!NOTE] +> **Zensical Documentation Tool**: +> [Zensical](https://zensical.org) is a static site generator and documentation compiler configured via `zensical.toml` that integrates [MkDocs](https://www.mkdocs.org/) and its plugin ecosystem (such as [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) and [macros](https://mkdocs-macros-plugin.readthedocs.io/)) under a simplified configuration structure. -- **Target configuration**: `zensical.toml` -- **Live Local Server**: Runs a hot-reloading development preview. - - *Command*: `hatch run project:serve-docs` (accessible at `http://localhost:8000`) -- **Static Assets Compilation**: Compiles the source files into HTML. - - *Command*: `hatch run project:build-docs` (outputs to the `site/` folder) +#### CLI Documentation Generation -______________________________________________________________________ +- **Tools / Methodology / Rationale**: Uses the `[typer](https://typer.tiangolo.com/)` utility to compile and output reference docs directly from the Python entrypoint `src/rustarium/__main__.py`. +- **Command**: `hatch run python:docs` +- **Expected Outputs & Locations**: A generated Markdown reference file at `.docs/cli.md`. + +#### Rust API Documentation Generation + +- **Tools / Methodology / Rationale**: Uses `cargo doc --workspace` to construct static HTML files documenting internal Rust API structures, crates, and types. +- **Command**: `hatch run rust:docs` +- **Expected Outputs & Locations**: Static HTML documentation directory located at `.docs/rust_api`. + +#### Project Website Compilation + +- **Tools / Methodology / Rationale**: Compiles the final developer documentation site via **Zensical**, incorporating the general Markdown guides, Python CLI docs, and Rust API reference documentation. +- **Command**: `hatch run project:docs` (or `hatch run all:docs` to generate Python and Rust docs and compile project docs together) +- **Expected Outputs & Locations**: Static build files compiled to the `site/` directory. + +> [!TIP] +> **Dynamic Coverage Report Inclusion**: +> When compiling the website locally, Zensical dynamically embeds the Python and Rust test coverage reports (extracted from `coverage/python/` and `coverage/rust/` respectively) into the final reference pages (`docs/reference/python_coverage.md` and `docs/reference/rust_coverage.md`). If the coverage reports have been generated locally, they will automatically be included in the compiled docs site. + +#### Live Development Preview Server + +- **Tools / Methodology / Rationale**: Launches a hot-reloading web server to preview changes locally in real-time. +- **Command**: + - Local Project Server: `hatch run project:docs-serve` + - Global Orchestrator: `hatch run all:docs-serve` +- **Expected Outputs & Locations**: Hot-reloading site hosted locally at `http://localhost:8000`. + +### Build & Distribution Workflows + +These workflows handle compiling code, bundling extension modules, and building containerized runtimes for distribution. + +#### Maturin Python Package Build + +- **Tools / Methodology / Rationale**: Uses Maturin and Hatchling (configured under `[build-system]` and `[tool.maturin]` in `pyproject.toml`) to compile Rust core extension bindings (`rustarium._rust`) and bundle Python distribution wheel packages. +- **Command**: `hatch build` +- **Expected Outputs & Locations**: Built source distributions and `.whl` files output to the `dist/` directory. + +#### OCI Container Image Build + +- **Tools / Methodology / Rationale**: Executes a Docker build to compile the multi-stage production image, tagging the result using metadata parameters. +- **Command**: `hatch run oci:build` +- **Expected Outputs & Locations**: Local Docker image compiled and tagged as `rustarium:latest` (configured via `{env:OCI_IMAGE}`). ## CI/CD Workflows -We maintain high quality gates using git workflows, automated reviews, and GitHub Actions pipelines. +We maintain high quality gates using git workflows, automated reviews, and [GitHub Actions](https://github.com/features/actions) pipelines. ### Version Control Standards @@ -533,27 +2468,120 @@ We maintain high quality gates using git workflows, automated reviews, and GitHu ### GitHub Actions Architecture -Our pipelines use a modular approach to avoid duplication: +Our pipelines use a highly modular and DRY architecture to avoid duplication of setup steps: + +- **Tools**: [GitHub Actions](https://github.com/features/actions) -- **Tools**: GitHub Actions - **Configuration / Manifest Files**: Reusable actions under `.github/actions/...` and triggers under `.github/workflows/...` -- **Workflow & Commands**: - - **Reusable Actions (`.github/actions/...`)**: Common build, cache, and test configuration tasks. - - **Workflows (`.github/workflows/...`)**: Triggered pipelines separated into: - - **Core Pipelines**: - - `development.yml`: PR tests, checks, and docs preview. - - `main.yml`: Merges to main; updates latest docs. - - `nightly.yml`: Deep vulnerabilities and regression test schedules. - - `release.yml`: Tag pushes (`v*.*.*`); packages binary builds. - - `weekly.yml`: Dependency and environment health verification. - - **Utility Workflows**: - - `development_cleanup.yml`: Cleans up transient PR doc deployments. + +- **Python Versioning & Parameters**: + + - All composite actions (Python, Rust, OCI, and Project) support an optional `python-version` parameter. + - If omitted, actions standardize on the oldest supported version (default: `"3.10"`). + - All composite actions using change detection (`[dorny/paths-filter](https://github.com/dorny/paths-filter)`) support a `force-run` parameter (default: `"false"`). When set to `"true"`, it bypasses path-filtering check gates and executes the steps unconditionally (used in scheduled and release workflows). + +- **OCI Tools Native Execution**: + + - The repository utilizes unified platform-agnostic OCI runner logic (`scripts/run_oci.py`). + - When running in CI under `.github/actions/oci/`, the actions natively install OCI scanning and linting tools (`[hadolint](https://github.com/hadolint/hadolint)`, `[dclint](https://github.com/zavoloklom/docker-compose-linter)`, `[dockle](https://github.com/goodwithtech/dockle)`, `[trivy](https://trivy.dev/)`, and `[container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)`) on the runner. + - This native pre-installation ensures that `run_oci.py` executes these binaries directly on the host machine, bypassing the performance overhead and Docker socket mounting requirements of containerized container-in-container execution. + + > [!WARNING] + > **Tool Version Drift Risk**: + > Running these tools natively in CI while developers run them locally via Docker fallback containers (e.g., `aquasec/trivy:latest`) can lead to version drift. To prevent the *"it passes locally but fails in CI"* issue: + > + > 1. Keep your system-installed binaries updated to match the versions used in CI workflows (defined in the OCI composite actions). + > 1. Periodically pull the latest container images locally (`docker pull aquasec/trivy:latest`) to keep Docker fallbacks in sync with CI runner environments. + +- **Utility Actions (`.github/actions/utility/...`)**: + + - `setup-python`: Sets up Python, and installs uv and Hatch. + - `setup-rust`: Sets up the Rust toolchain and invokes `setup-python` to establish the Hatch orchestrator. + +- **Environment Actions (`.github/actions/[env]/...`)**: + + - Partitioned into folders for each environment: `python`, `rust`, `oci`, and `project`. + - Inside each environment, standard actions run specific scripts: + - `quality`: Runs formatting, linting, and type checking. + - `security`: Runs dependency audits, secrets checks, and security linters. + - `tests`: Runs unit, integration, and E2E tests, accepting `test-level`, `test-category`, and `generate-coverage` inputs. + - `build`: Compiles wheels (Python), workspace crates (Rust), container images (OCI), or all elements (Project). + - `publish`: Publishes release packages to [PyPI](https://pypi.org/) (Python) or container images to [GHCR](https://github.com/features/packages) (OCI). + +- **Workflows (`.github/workflows/...`)**: Triggered pipelines separated into: + + - **Core Pipelines**: + - `pipeline-development.yml`: PR checks (quality, security, package build, tests, and documentation previews). + - `pipeline-main.yml`: Triggered on push to `main` branch (runs full checks and deploys latest docs). + - `pipeline-nightly.yml`: Nightly regression tests, vulnerability audits, and nightly releases. + - `pipeline-release.yml`: Release tag pushes (`v*.*.*`); packages binary builds, attests them, publishes to PyPI and GHCR, and creates releases. + - `pipeline-weekly.yml`: Scheduled weekly checks to verify environment health. + - **Utility Workflows**: + - `util-development-cleanup.yml`: Cleans up transient PR doc deployments. + - `util-pr-comment.yml`: Securely posts PR comments (build status, compiled coverage summary, documentation previews, and build packages) to avoid fork permission limits. + +### Local Workflow Testing with `act` + +You can test and validate [GitHub Actions](https://github.com/features/actions) workflows locally on your development machine using [nektos/act](https://github.com/nektos/act). This ensures that workflows run correctly before you push changes to GitHub. + +#### Prerequisites + +1. Install **[Docker](https://www.docker.com/)** (required by `act` to spin up runner containers). +1. Install `act` using your package manager: + - macOS ([Homebrew](https://brew.sh/)): `brew install act` + - Linux (curl): `curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash` + +> [!IMPORTANT] +> **Apple Silicon (M-series Chips) Emulation**: +> If you are on an Apple Silicon Mac, you must specify the target execution architecture using `--container-architecture linux/amd64`. This ensures that `act` pulls the `amd64` container image and installs pre-compiled `x86_64` wheels (such as `taplo`), bypassing compile-from-source errors due to missing arm64 wheels. + +#### Running Workflows Locally + +Run `act` from the repository root: + +- **List all jobs**: + ```bash + act -l + ``` +- **Run the default (pull_request) event (runs Development Pipeline)**: + ```bash + act pull_request + ``` +- **Run a specific job (e.g., project-quality)**: + ```bash + act -j project-quality + ``` +- **Dry-run a workflow (displays steps without execution)**: + ```bash + act -n + ``` + +#### Mocking Event Payloads (Change Detection) + +Because composite actions use `dorny/paths-filter` to detect path-level changes, running `act` directly will fail if the required event metadata is missing. You can provide a mock payload (`event.json`) to simulate the GitHub event context: + +1. Create an `event.json` in the root of the repository: + ```json + { + "repository": { + "default_branch": "main" + } + } + ``` +1. Pass the payload file using the `-e` flag: + ```bash + act push -W .github/workflows/pipeline-main.yml -j project-quality -e event.json --container-architecture linux/amd64 + ``` + +> [!NOTE] +> `act` runs steps inside Docker containers that simulate GitHub environments. By default, it uses a medium-sized Ubuntu image, but you can specify a fuller image using `act -P ubuntu-latest=catthehacker/ubuntu:act-latest`. ### Security & Code Scanning Gates -- **Tools**: [Semgrep](https://semgrep.dev/), [Dependabot](https://github.com/dependabot), [Trufflehog](https://github.com/trufflesecurity/trufflehog), and [Trivy](https://github.com/aquasecurity/trivy) -- **Workflow & Commands**: - - Automatic background audits for repository code issues, secret leaks, and package vulnerabilities during PR checks. +- **Tools**: [detect-secrets](https://github.com/Yelp/detect-secrets) (secret scanning), [checkov](https://www.checkov.io/) (infrastructure auditing), [semgrep](https://semgrep.dev/) (semantic scanning), [pip-audit](https://github.com/pypa/pip-audit) (Python package audits), `cargo-audit` / `cargo-deny` (Rust crate audits), and [trivy](https://trivy.dev/) / [dockle](https://github.com/goodwithtech/dockle) (OCI image scanning). +- **Workflow & Rationale**: + - **Secret Gating**: [detect-secrets](https://github.com/Yelp/detect-secrets) runs locally and in PR gates against the committed `.detect-secrets.scan.json` baseline to prevent credential leaks. + - **Static Analysis & CVE Auditing**: [Semgrep](https://semgrep.dev/), [Trivy](https://trivy.dev/), [Checkov](https://www.checkov.io/), [pip-audit](https://github.com/pypa/pip-audit), and the Rust Cargo audits run automatically as background checks on every pull request to guarantee compliance with our security baseline. ______________________________________________________________________ @@ -561,6 +2589,22 @@ For additional assistance, please refer to our [SUPPORT.md](SUPPORT.md). ## File: CONTRIBUTING.md + + # Contributing to rustarium First off, thank you for considering contributing to `rustarium`! It's people like you that make this project great. @@ -609,7 +2653,7 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ### 3. Making Changes 1. **Fork the Repository:** Fork the `rustarium` repository to your GitHub account. -1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/add-new-feature`). +1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/wasm-sandbox-support`). 1. **Write Code:** Implement your changes, adhering to the project's coding standards. 1. **Write Tests:** Add unit tests or integration tests for your changes to ensure stability. 1. **Run Tests:** Ensure all tests and linters pass locally before committing. @@ -617,14 +2661,13 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ### 4. Committing Your Changes - Write clear, concise commit messages. -- We recommend using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: add support for X`, `fix: resolve issue with Y`). -- If you are adding a new file, please include the appropriate Apache 2.0 copyright and license header at the top. +- We recommend using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: add wasm sandbox support`, `fix: resolve memory leak in orchestrator`). ### 5. Submitting a Pull Request -1. **Push your branch:** `git push origin your-branch-name`. +1. **Push your branch:** `git push origin feat/wasm-sandbox-support`. 1. **Open a Pull Request:** Open a PR against the `main` branch of the upstream repository. -1. **Fill out the PR Template:** Provide a clear description of your changes, link to any relevant issues (e.g., `Closes #123`), and complete any required checklists. +1. **Fill out the PR Template:** Provide a clear description of your changes, link to any relevant issues (e.g., `Closes #42`), and complete any required checklists. 1. **Pass CI:** Ensure all GitHub Actions CI checks pass. 1. **Review:** Address any feedback from the maintainers. Once approved and checks pass, a maintainer will merge your PR. @@ -634,20 +2677,34 @@ By contributing to `rustarium`, you agree that your contributions will be licens ## File: SECURITY.md -# Security Policy for `rustarium` + -| Version | Supported | -| :------------------------------ | :----------------- | -| `{{current_major_version}}.x` | :white_check_mark: | -| `< {{current_major_version}}.0` | :x: | +# Security Policy for Rustarium -*(Note: Replace the table contents with your actual versioning scheme once released.)* +We take the security of Rustarium seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability. + +## Supported Versions + +Please check the table below for the versions of Rustarium that are currently being supported with security updates. + +| Version | Supported | +| :-------- | :----------------- | +| `0.1.x` | :white_check_mark: | +| `< 0.1.0` | :x: | ## Reporting a Vulnerability @@ -657,7 +2714,7 @@ Please check the table below for the versions of `rustarium` that are currently If you discover a security vulnerability, please bring it to our attention right away using one of the following methods: 1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/markurtz/rustarium/security/advisories)** of this repository. -1. **Email:** Send your report directly to **contact the maintainers**. +1. **Direct Message:** Send a message directly to the maintainer's GitHub user account, **[markurtz](https://github.com/markurtz)**, if applicable or through other provided direct pathways for the user. ### What to Include in Your Report @@ -667,7 +2724,7 @@ To help us resolve the issue quickly, please include the following information: - **Detailed description** of the vulnerability and its potential impact. - **Step-by-step instructions** to reproduce the issue. - **Proof of Concept (PoC)** code or screenshots, if available. -- **Environment details** (e.g., version of `rustarium`, OS, Python version, relevant configurations). +- **Environment details** (e.g., version of Rustarium, OS, Python version, relevant configurations). ## Triage and Resolution Process @@ -682,22 +2739,38 @@ We will handle your report with strict confidentiality. Our process is as follow **In Scope:** -- Vulnerabilities within the core `rustarium` codebase. +- Vulnerabilities within the core Rustarium codebase. - Security issues resulting from our default configurations or execution paths. **Out of Scope:** - Theoretical issues without a reproducible PoC. -- Vulnerabilities in third-party dependencies that are not exploitable through `rustarium`. -- Issues requiring the victim to intentionally clone and run `rustarium` against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. +- Vulnerabilities in third-party dependencies that are not exploitable through Rustarium. +- Issues requiring the victim to intentionally clone and run Rustarium against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. *(Note: We currently do not operate a bug bounty program. Disclosures are greatly appreciated but are not eligible for financial rewards at this time.)* ## File: SUPPORT.md -# Support for `rustarium` + + +# Support for Rustarium + +We are excited to have you use Rustarium! If you need help, please follow these guidelines to ensure you get support quickly and efficiently. ## Security Vulnerabilities @@ -722,10 +2795,11 @@ If you cannot find an answer in the documentation or existing issues, please ope | :--------------------- | :-------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | **Bug Report** | [GitHub Issues](https://github.com/markurtz/rustarium/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | | **Feature Request** | [GitHub Issues](https://github.com/markurtz/rustarium/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | -| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/rustarium/discussions/new) | Start a discussion for questions about how to use `rustarium`, architecture queries, or advice. | +| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/rustarium/discussions/new) | Start a discussion for questions about how to use Rustarium or for general advice. | Feel free to join the conversation on GitHub Discussions to connect with other users and maintainers. ## Commercial Support -At this time, there is no official commercial support available for `rustarium`. Support is provided on a best-effort basis by the open-source community and maintainers. +At this time, there is no official commercial support available for Rustarium. Support is provided on a best-effort basis by the open-source community and maintainers. + diff --git a/llms.txt b/llms.txt index 4f4424e..b005a4c 100644 --- a/llms.txt +++ b/llms.txt @@ -2,19 +2,36 @@ > A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust. -`rustarium` is designed to eliminate boilerplate and enforce consistency across an organization's repositories. -For comprehensive context and full documentation, please see [llms-full.txt](llms-full.txt). +`rustarium` provides secure execution environments, logging, and metrics for Python and WebAssembly processes. +For complete, merged context containing the full codebase and all guides, see [llms-full.txt](llms-full.txt). -## Docs -- [Home](https://markurtz.github.io/rustarium/): Documentation site home page -- [Getting Started](https://markurtz.github.io/rustarium/getting-started/): Installation, quickstart, and workflow guides +## Primary Documentation Links +- [Documentation Home](https://markurtz.github.io/rustarium/): Main site home page +- [Getting Started](https://markurtz.github.io/rustarium/getting-started/): Onboarding, installation, and setup - [Guides](https://markurtz.github.io/rustarium/guides/): How-to guides for common tasks -- [API Reference](https://markurtz.github.io/rustarium/reference/): Full API reference documentation +- [API Reference](https://markurtz.github.io/rustarium/reference/): Complete API reference -## Repository Files -- [README.md](https://github.com/markurtz/rustarium/blob/main/README.md): Project overview and quick start -- [AGENTS.md](https://github.com/markurtz/rustarium/blob/main/AGENTS.md): AI agent coding instructions -- [DEVELOPING.md](https://github.com/markurtz/rustarium/blob/main/DEVELOPING.md): Developer setup guide +## Architectural Topography -## Optional -- [Full Documentation](https://markurtz.github.io/rustarium/llms-full.txt): The complete concatenated documentation for rustarium +### Configuration & Entrypoints +- [pyproject.toml](pyproject.toml): Project settings, environment scripts, and Hatch metadata. +- [AGENTS.md](AGENTS.md): Foundational agent development guidelines, build commands, and security boundaries. +- [CLAUDE.md](CLAUDE.md): Direct pointer routing assistants to `AGENTS.md`. +- [src/rustarium/__main__.py](src/rustarium/__main__.py): Standalone CLI entrypoint. + +### Core Python Modules +- [src/rustarium/settings.py](src/rustarium/settings.py): Pydantic schemas validating configuration settings. +- [src/rustarium/logging.py](src/rustarium/logging.py): Loguru logging setup and environment metrics. +- [src/rustarium/client.py](src/rustarium/client.py): Python orchestrator client interface. +- [src/rustarium/exceptions.py](src/rustarium/exceptions.py): Orchestrator-specific exception types. + +### Core Rust Extension (Maturin Bindings) +- [Cargo.toml](Cargo.toml): Cargo workspace manifest. +- [crates/rustarium-core/Cargo.toml](crates/rustarium-core/Cargo.toml): Core Rust crate manifest. +- [crates/rustarium-core/src/lib.rs](crates/rustarium-core/src/lib.rs): Rust module bindings and PyO3 setup. +- [crates/rustarium-core/src/sum.rs](crates/rustarium-core/src/sum.rs): Sandboxed computation functions. + +### Testing Suite Tiers +- [tests/python/unit/](tests/python/unit/): Python unit test suite. +- [tests/python/integration/](tests/python/integration/): Python integration test suite. +- [tests/e2e/](tests/e2e/): High-level black-box orchestrator integration tests. diff --git a/pyproject.toml b/pyproject.toml index 758df7f..74d009a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,25 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + [build-system] -requires = ["maturin>=1.5,<2.0"] -build-backend = "maturin" +requires = ["gitversioned[maturin]>=0.1.0", "maturin>=1.5,<2.0"] +build-backend = "gitversioned.plugins.maturin_plugin" [project] name = "rustarium" dynamic = ["version"] -description = "A high-performance, telemetrized process orchestrator and sandbox for Python, forged in Rust." +description = "A high-performance, telemetrized process orchestrator and sandbox for Python and WASM, forged in Rust." readme = "README.md" requires-python = ">=3.10" license = { text = "Apache-2.0" } @@ -19,10 +33,14 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ - "loguru>=0.7.0", - "pydantic>=2.0.0", - "pydantic-settings>=2.0.0", - "typer>=0.12.0", + "loguru~=0.7", + "packaging>=26.0", + "pydantic~=2.0", + "pydantic-settings~=2.0", + "setuptools>=64.0.0", + "tomli~=2.0; python_version < '3.11'", + "tstr>=0.4.1", + "typer>=0.12", ] [project.optional-dependencies] @@ -30,11 +48,11 @@ opentelemetry = ["opentelemetry-api>=1.0.0", "opentelemetry-sdk>=1.0.0"] [dependency-groups] test = [ - "pytest>=9.0,<10", - "pytest-asyncio>=1.3,<2", - "pytest-cov>=7.1,<8", - "pytest-mock>=3.15,<4", - "respx>=0.23,<1", + "pytest>=9.0.3,<10", + "pytest-asyncio>=1.4.0,<2", + "pytest-cov>=7.1.0,<8", + "pytest-mock>=3.15.1,<4", + "respx>=0.23.1,<1", "pytest-xdist>=3.5,<4", ] lint = [ @@ -56,6 +74,9 @@ docs = [ "mkdocs-gen-files>=0.5.0", "mkdocstrings>=0.24.0", "mkdocstrings-python>=1.9.0", + # Temporary bridge: Zensical compatible fork of mike. + # Update to Zensical native versioning once stabilized. + "mike @ git+https://github.com/squidfunk/mike.git", ] security = [ "detect-secrets>=1.5,<2", @@ -63,7 +84,7 @@ security = [ "semgrep>=1.73,<2", "pip-audit>=2.7,<3", ] -environment = ["hatch>=1.12.0"] +environment = ["hatch>=1.12.0", "gitversioned>=0.2.0"] dev = [ { include-group = "test" }, { include-group = "lint" }, @@ -81,6 +102,11 @@ Repository = "https://github.com/markurtz/rustarium.git" Issues = "https://github.com/markurtz/rustarium/issues" Documentation = "https://markurtz.github.io/rustarium/" + +# ============================================================================== +# Maturin Crate Configurations +# ============================================================================== + [tool.maturin] features = ["pyo3/extension-module"] manifest-path = "crates/rustarium-core/Cargo.toml" @@ -88,13 +114,32 @@ module-name = "rustarium._rust" python-source = "src" include = ["src/rustarium/version.py"] + +# ============================================================================== +# Versioning +# ============================================================================== + [tool.gitversioned] +output = "src/rustarium/version.py" source_type = ["tag"] +format_dev = "dev{ref.total_commits}+{ref.short_sha}" +format_pre = "a{ref.timestamp:%Y%m%d}" +output_strategies = { type = "regex", pattern = '(?m)^__version__\s*=\s*"(?P[^"]+)"' } [tool.gitversioned.auto_increment] pre = "minor" dev = "patch" +[tool.gitversioned.overrides.cargo] +output = "Cargo.toml" +version_standard = "semver2" +output_strategies = { type = "regex", pattern = '''(?ms)^\[workspace\.package\].*?^(\s*version\s*=\s*)(["'])(?P[^"']+)\2''' } + +[tool.gitversioned.overrides.docker] +output = "Dockerfile" +output_strategies = { type = "regex", pattern = '''(?m)^(\s*ARG\s+VERSION\s*=\s*)(?P[^\s\n]+)''' } + + # ============================================================================== # Hatch Environment Configurations # ============================================================================== @@ -103,13 +148,16 @@ dev = "patch" dependency-groups = ["dev"] [tool.hatch.envs.default.env-vars] +COVERAGE_CORE = "pytrace" +GITVERSIONED__LOGGING__LEVEL = "ERROR" +PYTHONPATH = "." PYTHON_TARGETS = "docs examples scripts src tests" RUST_MANIFEST = "crates/rustarium-core/Cargo.toml" OCI_IMAGE = "rustarium:latest" MDFORMAT_TARGETS = ".devcontainer/ .github/ crates/ docs/*.md docs/community/ docs/examples/ docs/getting-started/ docs/guides/ examples/ scripts/ src/ tests/ *.md" YAML_TARGETS = ".devcontainer/ .github/ crates/ docs/ examples/ scripts/ src/ tests/ *.yml *.yaml" TOML_TARGETS = ".devcontainer/ .github/ crates/ docs/ examples/ scripts/ src/ tests/ *.toml" -PYTHON_SRC = "src" +PYTHON_SRC = "rustarium" PYTHON_UNIT_TESTS = "tests/python/unit" PYTHON_INT_TESTS = "tests/python/integration" PYTHON_COV_DIR = "coverage/python" @@ -123,6 +171,7 @@ GENERATE_DOC_TESTS_SCRIPT = "scripts/generate_doc_tests.py" [tool.hatch.envs.all] template = "default" +builder = true [tool.hatch.envs.all.scripts] lint = [ @@ -150,10 +199,10 @@ security = [ "hatch run project:security {args}", ] quality = [ - "hatch run all:format {args}", - "hatch run all:lint {args}", - "hatch run all:types {args}", - "hatch run all:security {args}", + "hatch run python:quality {args}", + "hatch run rust:quality {args}", + "hatch run oci:quality {args}", + "hatch run project:quality {args}", ] build = [ "hatch build {args}", @@ -226,6 +275,7 @@ docs-serve = ["hatch run docs {args}", "hatch run project:docs-serve {args}"] # ============================================================================== [tool.hatch.envs.python] template = "default" +builder = true [tool.hatch.envs.python.scripts] lint = [ @@ -242,34 +292,40 @@ security = [ "pip-audit {args}", "ruff check --select S {args:{env:PYTHON_TARGETS}}", ] -tests-func = "pytest {args:{env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS}}" +quality = [ + "hatch run python:format {args}", + "hatch run python:lint {args}", + "hatch run python:types {args}", + "hatch run python:security {args}", +] +tests-func = "python -m pytest {env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS} {args}" tests-func-cov = [ "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", - "pytest --cov={env:PYTHON_SRC} --cov-context=test --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-func.md {args:{env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS}}", + "python -m pytest --cov={env:PYTHON_SRC} --cov-context=test --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-func.md {env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS} {args}", "coverage report --contexts=\".*tests/python/unit/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-unit.md", "coverage report --contexts=\".*tests/python/integration/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-int.md", "python -c \"import pathlib; [p.write_text('\\n'.join(line for line in p.read_text().splitlines() if not line.startswith('Combined'))) for p in (pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-unit.md'), pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-int.md')) if p.exists()]\"", ] -tests-unit = "pytest {args:{env:PYTHON_UNIT_TESTS}}" +tests-unit = "python -m pytest {env:PYTHON_UNIT_TESTS} {args}" tests-unit-cov = [ "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", - "pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-unit.md {args:{env:PYTHON_UNIT_TESTS}}", + "python -m pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-unit.md {env:PYTHON_UNIT_TESTS} {args}", ] -tests-int = "pytest {args:{env:PYTHON_INT_TESTS}}" +tests-int = "python -m pytest {env:PYTHON_INT_TESTS} {args}" tests-int-cov = [ "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", - "pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-int.md {args:{env:PYTHON_INT_TESTS}}", + "python -m pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-int.md {env:PYTHON_INT_TESTS} {args}", ] tests-e2e = [ "hatch build", "pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium", - "pytest {args:{env:E2E_TESTS}}", + "python -m pytest {env:E2E_TESTS} {args}", ] tests-e2e-cov = [ "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", "hatch build", "pip install --force-reinstall --no-deps --no-index --find-links=dist rustarium", - "pytest --cov=rustarium --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-e2e.md {args:{env:E2E_TESTS}}", + "python -m pytest --cov=rustarium --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-e2e.md {env:E2E_TESTS} {args}", ] docs = [ "python -c \"import pathlib; pathlib.Path('.docs').mkdir(exist_ok=True)\"", @@ -295,6 +351,12 @@ format = [ types = "cargo check --manifest-path {env:RUST_MANIFEST} --all-targets {args}" security = ["cargo bin cargo-audit {args}", "cargo bin cargo-deny check {args}"] install-tools = ["cargo install --locked cargo-run-bin"] +quality = [ + "hatch run rust:format {args}", + "hatch run rust:lint {args}", + "hatch run rust:types {args}", + "hatch run rust:security {args}", +] tests-func = "cargo test --manifest-path {env:RUST_MANIFEST} --all-targets {args}" tests-func-cov = [ "python -c \"import pathlib; pathlib.Path('{env:RUST_COV_DIR}').mkdir(parents=True, exist_ok=True)\"", @@ -313,7 +375,7 @@ tests-int-cov = [ "cargo llvm-cov --manifest-path {env:RUST_MANIFEST} --test '*' --json --output-path {env:RUST_COV_DIR}/coverage_tests-int.json {args}", "covgate check {env:RUST_COV_DIR}/coverage_tests-int.json --markdown-output {env:RUST_COV_DIR}/coverage_tests-int.md", ] -tests-e2e = "echo '[INFO] No E2E tests are defined for the Rust environment'" +tests-e2e = "echo '[INFO] No E2E tests are defined for the Rust environment' {args}" tests-e2e-cov = [ "hatch run rust:tests-e2e {args}", "echo '[INFO] No coverage analysis is applicable for E2E tests in the Rust environment'", @@ -340,6 +402,12 @@ security = [ "python {env:RUN_OCI_SCRIPT} dockle {args}", "python {env:RUN_OCI_SCRIPT} trivy {args}", ] +quality = [ + "hatch run oci:format {args}", + "hatch run oci:lint {args}", + "hatch run oci:types {args}", + "hatch run oci:security {args}", +] tests-func = [ "hatch run oci:tests-unit {args}", "hatch run oci:tests-int {args}", @@ -358,7 +426,7 @@ tests-int-cov = [ "hatch run oci:tests-int {args}", "echo '[INFO] No coverage analysis is applicable for integration tests in the OCI environment'", ] -tests-e2e = ["hatch run oci:build", "python {env:RUN_OCI_SCRIPT} cstest {args}"] +tests-e2e = "python {env:RUN_OCI_SCRIPT} cstest {args}" tests-e2e-cov = [ "hatch run oci:tests-e2e {args}", "echo '[INFO] No coverage analysis is applicable for E2E tests in the OCI environment'", @@ -384,40 +452,58 @@ format = [ "yamlfix {args:{env:YAML_TARGETS}}", "taplo fmt {args:{env:TOML_TARGETS}}", ] -types = "echo '[INFO] Type checking is not applicable for the project environment'" +types = "echo '[INFO] Type checking is not applicable for the project environment' {args}" security = [ "detect-secrets scan --baseline {env:SECRETS_BASELINE} {args:.}", "python -m checkov.main --quiet {args:-d .}", ] +quality = [ + "hatch run project:format {args}", + "hatch run project:lint {args}", + "hatch run project:types {args}", + "hatch run project:security {args}", +] security-update = ["detect-secrets scan {args:.} > {env:SECRETS_BASELINE}"] -tests-func = ["hatch run project:tests-unit", "hatch run project:tests-int"] +tests-func = [ + "hatch run project:tests-unit {args}", + "hatch run project:tests-int {args}", +] tests-func-cov = [ - "hatch run project:tests-unit-cov", - "hatch run project:tests-int-cov", + "hatch run project:tests-unit-cov {args}", + "hatch run project:tests-int-cov {args}", ] tests-unit = "echo '[INFO] No unit tests are defined for the project environment'" tests-unit-cov = [ - "hatch run project:tests-unit", + "hatch run project:tests-unit {args}", "echo '[INFO] No coverage analysis is applicable for unit tests in the project environment'", ] tests-int = [ "python {env:GENERATE_DOC_TESTS_SCRIPT} {args:{env:PYTHON_TARGETS}}", - "hatch -e python run pytest {env:DOC_TESTS_PATH} {args}", + "python -m pytest {env:DOC_TESTS_PATH} {args}", ] tests-int-cov = [ - "hatch run project:tests-int", + "hatch run project:tests-int {args}", "echo '[INFO] No coverage analysis is applicable for integration tests in the project environment'", ] -tests-e2e = "python {env:CHECK_LINKS_SCRIPT} {args:{env:MDFORMAT_TARGETS}}" +link-checks = "python {env:CHECK_LINKS_SCRIPT} {args:{env:MDFORMAT_TARGETS}}" +link-checks-cov = [ + "hatch run project:link-checks {args}", + "echo '[INFO] No coverage analysis is applicable for link checks in the project environment'", +] +tests-e2e = ["python -m pytest --import-mode=importlib examples {args}"] tests-e2e-cov = [ - "hatch run project:tests-e2e", + "hatch run project:tests-e2e {args}", "echo '[INFO] No coverage analysis is applicable for E2E tests in the project environment'", ] docs = [ + "python docs/scripts/gen_ref_pages.py generate", "python -m zensical {args:build}", "python -c \"import shutil, pathlib; src = pathlib.Path('.docs/rust_api/doc'); dst = pathlib.Path('site/reference/rust_api'); dst.mkdir(parents=True, exist_ok=True); [shutil.copytree(p, dst / p.name, dirs_exist_ok=True) if p.is_dir() else shutil.copy2(p, dst / p.name) for p in src.glob('*')] if src.exists() else None\"", ] -docs-serve = ["python -m zensical serve"] +docs-serve = [ + "python docs/scripts/gen_ref_pages.py generate", + "python -m zensical serve {args}", +] [tool.ty.src] @@ -525,6 +611,9 @@ select = [ "examples/**/*.py" = [ "T201", # allow print in examples "INP001", # allow implicit namespace packages + "S603", # allow subprocess check with untrusted input + "S607", # allow starting a process with a partial executable path + "S101", # allow asserts in tests/examples ] "scripts/**/*.py" = [ "T201", # allow print in scripts @@ -545,6 +634,7 @@ known-first-party = ["rustarium", "tests"] [tool.pytest.ini_options] addopts = "-s -vvv --cache-clear" asyncio_mode = "auto" +log_level = "WARNING" markers = [ "smoke: quick tests to check basic functionality", "sanity: detailed tests to ensure major functions work correctly", diff --git a/scripts/README.md b/scripts/README.md index ca384dc..fc3eb7e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,6 +1,25 @@ -# Utility Scripts + + +# Rustarium - Utility Scripts + +This directory contains utility scripts designed to assist with local development, maintenance, and automation tasks for Rustarium. > [!NOTE] > These scripts are intended for developer and CI/CD use only and are **not** distributed as part of the final application package. @@ -15,10 +34,10 @@ Scripts should generally be executed from the root of the repository to ensure r ```bash # Example -uv run scripts/bootstrap.py +./scripts/.sh ``` -Ensure Bash scripts have execution permissions before running: +Ensure the script has execution permissions before running: ```bash chmod +x scripts/.sh @@ -28,7 +47,7 @@ chmod +x scripts/.sh If you are adding or modifying a script in this directory, please ensure it adheres to the following best practices: -- **Prefer Python (`.py`) or Bash (`.sh`):** Python is preferred for complex logic (managed via `uv run`). Bash is acceptable for simple wrappers. +- **Prefer Bash (`.sh`) or Python (`.py`):** These languages provide the best cross-platform compatibility. - **Fail Fast (Bash):** Always begin Bash scripts with `set -euo pipefail` to ensure they exit immediately on errors, undefined variables, or pipeline failures. - **Environment Variables:** If your script requires secrets or environment-specific configurations, document them at the top of the script and ensure they align with the `.env.example` file. - **Provide Help:** Scripts should ideally accept a `-h` or `--help` flag that outputs usage instructions. @@ -36,9 +55,7 @@ If you are adding or modifying a script in this directory, please ensure it adhe ## Available Scripts -| Script | Description | -| :------------- | :----------------------------------------------------------------------------------------------------------------------- | -| `bootstrap.py` | Interactively bootstraps a new repository from this template by replacing placeholders and configuring project features. | +*Currently, there are no utility scripts in this directory.* ## Contributing diff --git a/scripts/__init__.py b/scripts/__init__.py index e69de29..37c5ce5 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Unless otherwise noted, all files in this directory and its subdirectories +# are licensed under the Apache License, Version 2.0. + diff --git a/scripts/check_links.py b/scripts/check_links.py index 80adbe3..2432dbd 100644 --- a/scripts/check_links.py +++ b/scripts/check_links.py @@ -1,17 +1,3 @@ -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """ Platform-agnostic utility to scan and check URLs in markdown files. @@ -107,7 +93,7 @@ def main( "--file-types", ".md", "--exclude-patterns", - "localhost", + "localhost,127.0.0.1,actions/workflows,github.com/markurtz/rustarium/tree", ] + extra_options, check=True, diff --git a/scripts/generate_doc_tests.py b/scripts/generate_doc_tests.py index 15e82f5..2ea3c3e 100644 --- a/scripts/generate_doc_tests.py +++ b/scripts/generate_doc_tests.py @@ -1,17 +1,3 @@ -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """ Extract and generate test files from markdown documents. @@ -93,7 +79,6 @@ def _find_markdown_files(targets: list[str]) -> list[Path]: @app.callback(invoke_without_command=True) def run_generate_doc_tests( - ctx: typer.Context, # noqa: ARG001 targets_and_options: Annotated[ list[str] | None, typer.Argument( @@ -110,7 +95,6 @@ def run_generate_doc_tests( >>> runner = CliRunner() >>> result = runner.invoke(app, ["docs/"]) - :param ctx: CLI execution context. :param targets_and_options: Target paths or extra phmdoctest arguments. :return: None. """ diff --git a/scripts/run_oci.py b/scripts/run_oci.py index f8b4722..7766800 100644 --- a/scripts/run_oci.py +++ b/scripts/run_oci.py @@ -1,17 +1,3 @@ -# Copyright 2026 markurtz -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """ A unified platform-agnostic OCI runner script. @@ -489,10 +475,8 @@ def run_cstest(extra_args: list[str]) -> None: return if not check_image_exists(IMAGE_NAME): - logger.error( - f"Image {IMAGE_NAME} not found. Build it first (e.g. hatch run oci:build)." - ) - sys.exit(1) + logger.info(f"Image {IMAGE_NAME} not found. Building it first...") + subprocess.run(["docker", "build", "-t", IMAGE_NAME, "."], check=True) logger.info( f"Running container-structure-test via Docker with args {extra_args}..." diff --git a/src/rustarium/__init__.py b/src/rustarium/__init__.py index 6af657b..b25a8d5 100644 --- a/src/rustarium/__init__.py +++ b/src/rustarium/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """ Core initialization module for the rustarium package. @@ -70,3 +84,5 @@ def sum_as_string(a: int, b: int) -> str: "logger", "sum_as_string", ] + +configure_logger() diff --git a/src/rustarium/__main__.py b/src/rustarium/__main__.py index 60fa19d..638bc87 100644 --- a/src/rustarium/__main__.py +++ b/src/rustarium/__main__.py @@ -1,3 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """ Main entrypoint for the rustarium package. @@ -13,7 +27,6 @@ import typer from rustarium.logging import LoggingSettings, configure_logger, logger -from rustarium.settings import Settings from rustarium.version import __version__ __all__ = ["main"] @@ -24,21 +37,14 @@ ) -def _version_callback(value: bool) -> None: - """Callback to display the current version.""" - if value: - typer.echo(f"rustarium v{__version__}") - raise typer.Exit - - @app.callback(invoke_without_command=True) -def _main_callback( +def main_callback( ctx: typer.Context, - version: Annotated[ # noqa: ARG001 + version: Annotated[ bool | None, typer.Option( "--version", - callback=_version_callback, + "-v", is_eager=True, help="Show the application version and exit.", ), @@ -48,6 +54,13 @@ def _main_callback( Global setup for the CLI application. Initializes application settings and logging. """ + if version: + typer.echo(f"rustarium v{__version__}") + raise typer.Exit + if ctx.invoked_subcommand is None: + typer.echo(ctx.get_help()) + raise typer.Exit + configure_logger( LoggingSettings( enabled=True, @@ -56,11 +69,6 @@ def _main_callback( filter=("rustarium", "__main__"), ) ) - settings = Settings() - - if ctx.invoked_subcommand is None: - logger.info("Hello from rustarium v{}!", __version__) - logger.info("Settings: {}", settings) @app.command() diff --git a/src/rustarium/compat.py b/src/rustarium/compat.py index 51f4574..ee2ce8b 100644 --- a/src/rustarium/compat.py +++ b/src/rustarium/compat.py @@ -1,3 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """ Compatibility abstractions for optional dependencies. diff --git a/src/rustarium/logging.py b/src/rustarium/logging.py index 65588e8..5ae67f7 100644 --- a/src/rustarium/logging.py +++ b/src/rustarium/logging.py @@ -1,3 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """ Loguru-based logging configuration and environment settings for rustarium. @@ -9,11 +23,12 @@ from __future__ import annotations import contextlib +import functools import json import sys import traceback from collections.abc import Callable -from typing import Any, ClassVar, Literal +from typing import Any, ClassVar, Literal, TypeVar, cast, overload from loguru import logger from pydantic import Field, field_validator @@ -21,9 +36,15 @@ from rustarium.compat import opentelemetry_trace -__all__ = ["LoggingSettings", "configure_logger", "logger"] +__all__ = ["LoggingSettings", "autolog", "configure_logger", "logger"] + +_LOG_ENTRY_FORMAT: str = "Calling function '{name}' with args={args}, kwargs={kwargs}" +_LOG_EXIT_FORMAT: str = "Function '{name}' returned: {result}" +_LOG_EXCEPTION_FORMAT: str = "Exception occurred in function '{name}': {exception}" -_HANDLER_ID: int | None = None +_FuncT = TypeVar("_FuncT", bound=Callable[..., Any]) + +_state: dict[str, int | None] = {"handler_id": None} class LoggingSettings(BaseSettings): @@ -118,54 +139,6 @@ def _parse_sink(cls, value: Any) -> Any: return value -def _otel_formatter(record: dict[str, Any]) -> str: - # Format the log record as an OpenTelemetry compliant JSON string. - trace_id = span_id = trace_flags = None - - if opentelemetry_trace: - span = opentelemetry_trace.get_current_span() - context = span.get_span_context() - if context.is_valid: - trace_id = format(context.trace_id, "032x") - span_id = format(context.span_id, "016x") - trace_flags = format(context.trace_flags, "02x") - - log_record = { - "timestamp": record["time"].isoformat(), - "severity_text": record["level"].name, - "body": record["message"], - "resource": {"service.name": "rustarium"}, - "attributes": { - "module": record["name"], - "function": record["function"], - "line": record["line"], - **record["extra"], - }, - } - - if record.get("exception"): - exception = record["exception"] - log_record["attributes"]["exception.type"] = exception.type.__name__ - log_record["attributes"]["exception.message"] = str(exception.value) - log_record["attributes"]["exception.stacktrace"] = "".join( - traceback.format_exception( - exception.type, exception.value, exception.traceback - ) - ) - - if trace_id: - log_record.update( - { - "trace_id": trace_id, - "span_id": span_id, - "trace_flags": trace_flags, - } - ) - - # Escape braces so loguru doesn't interpret the JSON string as a format string - return json.dumps(log_record).replace("{", "{{").replace("}", "}}") + "\n" - - def configure_logger(settings: LoggingSettings | None = None) -> None: """ Initializes the loguru logger with the provided settings or from the environment. @@ -187,8 +160,6 @@ def configure_logger(settings: LoggingSettings | None = None) -> None: :raises ImportError: If OpenTelemetry formatting is explicitly enabled but the package is not installed. """ - global _HANDLER_ID # noqa: PLW0603 - settings = settings or LoggingSettings() if not settings.enabled: @@ -199,11 +170,11 @@ def configure_logger(settings: LoggingSettings | None = None) -> None: if settings.clear_loggers: logger.remove() - _HANDLER_ID = None - elif isinstance(_HANDLER_ID, int): + _state["handler_id"] = None + elif isinstance(_state["handler_id"], int): with contextlib.suppress(ValueError): - logger.remove(_HANDLER_ID) - _HANDLER_ID = None + logger.remove(_state["handler_id"]) + _state["handler_id"] = None use_otel = settings.otel_formatting == "enable" or ( settings.otel_formatting == "auto" and opentelemetry_trace is not None @@ -216,25 +187,159 @@ def configure_logger(settings: LoggingSettings | None = None) -> None: log_format = _otel_formatter if use_otel else settings.format filter_val = "rustarium" if settings.filter is True else settings.filter - if isinstance(filter_val, (list, tuple)): - prefixes = tuple(filter_val) + if isinstance(filter_val, (list, tuple, str)): + prefixes = ( + tuple(filter_val) + if isinstance(filter_val, (list, tuple)) + else (filter_val,) + ) def final_filter(record: dict[str, Any]) -> bool: return bool(record["name"] and record["name"].startswith(prefixes)) - elif isinstance(filter_val, str): - - def final_filter(record: dict[str, Any]) -> bool: - return bool(record["name"] and record["name"].startswith(filter_val)) - else: - final_filter = None if filter_val is False else filter_val # type: ignore[assignment] + final_filter = None if filter_val is False else filter_val - _HANDLER_ID = logger.add( # ty: ignore[no-matching-overload] - settings.sink, # type: ignore[arg-type] + _state["handler_id"] = logger.add( + cast("Any", settings.sink), level=settings.level, - filter=final_filter, # type: ignore[arg-type] - format=log_format, # type: ignore[arg-type] + filter=cast("Any", final_filter), + format=cast("Any", log_format), enqueue=settings.enqueue, **settings.kwargs, ) + + +@overload +def autolog(func: _FuncT) -> _FuncT: ... + + +@overload +def autolog( + func: None = None, + *, + exception_log_level: str | None = "ERROR", +) -> Callable[[_FuncT], _FuncT]: ... + + +def autolog( + func: _FuncT | None = None, + *, + exception_log_level: str | None = "ERROR", +) -> _FuncT | Callable[[_FuncT], _FuncT]: + """ + Decorate a function to log call inputs, outputs, and any raised exceptions. + + Examples: + Use as a direct decorator: + + >>> @autolog + ... def add(a: int, b: int) -> int: + ... return a + b + + Use as a decorator factory call with defaults: + + >>> @autolog() + ... def sub(a: int, b: int) -> int: + ... return a - b + + Use with a custom exception log level: + + >>> @autolog(exception_log_level="WARNING") + ... def divide(a: int, b: int) -> float: + ... return a / b + + :param func: Target function to wrap, defaults to None. + :type func: Callable | None + :param exception_log_level: Log level for exception reporting, + defaults to "ERROR". + :type exception_log_level: str | None + :return: The decorated wrapper or a decorator factory function. + :rtype: Callable + """ + + def decorator(func_to_wrap: _FuncT) -> _FuncT: + @functools.wraps(func_to_wrap) + def wrapper(*args: Any, **kwargs: Any) -> Any: + func_name = getattr(func_to_wrap, "__qualname__", "function") + logger.debug( + _LOG_ENTRY_FORMAT.format(name=func_name, args=args, kwargs=kwargs) + ) + try: + result = func_to_wrap(*args, **kwargs) + except Exception as error: + if exception_log_level == "ERROR": + logger.opt(exception=error).error( + _LOG_EXCEPTION_FORMAT.format(name=func_name, exception=error), + ) + elif exception_log_level is not None: + logger.log( + exception_log_level, + _LOG_EXCEPTION_FORMAT.format(name=func_name, exception=error), + ) + raise error + else: + logger.debug(_LOG_EXIT_FORMAT.format(name=func_name, result=result)) + return result + + return cast("_FuncT", wrapper) + + if func is None: + return decorator + return decorator(func) + + +def _otel_formatter(record: dict[str, Any]) -> str: + # Format the log record as an OpenTelemetry compliant JSON string. + trace_id = span_id = trace_flags = None + + if opentelemetry_trace: + span = opentelemetry_trace.get_current_span() + context = span.get_span_context() + if context.is_valid: + trace_id = format(context.trace_id, "032x") + span_id = format(context.span_id, "016x") + trace_flags = format(context.trace_flags, "02x") + + log_record = { + "timestamp": record["time"].isoformat(), + "severity_text": record["level"].name, + "body": record["message"], + "resource": {"service.name": "rustarium"}, + "attributes": { + "module": record["name"], + "function": record["function"], + "line": record["line"], + "process_id": record["process"].id, + **record["extra"], + }, + } + + if record.get("exception"): + exception = record["exception"] + log_record["attributes"]["exception.type"] = exception.type.__name__ + log_record["attributes"]["exception.message"] = str(exception.value) + log_record["attributes"]["exception.stacktrace"] = "".join( + traceback.format_exception( + exception.type, exception.value, exception.traceback + ) + ) + + if trace_id: + log_record.update( + { + "trace_id": trace_id, + "span_id": span_id, + "trace_flags": trace_flags, + } + ) + + # Escape braces so loguru doesn't interpret the JSON string as a format string + # Escape '<' and '>' to prevent loguru from interpreting them as color markup tags + return ( + json.dumps(log_record) + .replace("{", "{{") + .replace("}", "}}") + .replace("<", "\\<") + .replace(">", "\\>") + ) + "\n" diff --git a/src/rustarium/settings.py b/src/rustarium/settings.py index 2f3d141..655c96c 100644 --- a/src/rustarium/settings.py +++ b/src/rustarium/settings.py @@ -1,3 +1,17 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """ Settings configuration for the rustarium application. @@ -15,6 +29,9 @@ from pydantic import Field from pydantic_settings import ( BaseSettings, + CliSettingsSource, + PydanticBaseSettingsSource, + PyprojectTomlConfigSettingsSource, SettingsConfigDict, ) @@ -44,6 +61,9 @@ class Settings(BaseSettings): populate_by_name=True, validate_assignment=True, env_prefix="RUSTARIUM__", + cli_prefix="rustarium_", + cli_parse_args=True, + pyproject_toml_table_header=("tool", "rustarium"), ) """Pydantic config dict dictating environment prefixes and validation.""" @@ -59,6 +79,41 @@ class Settings(BaseSettings): description="The current deployment environment of the application.", ) + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> tuple[PydanticBaseSettingsSource, ...]: + """ + Customize configuration sources and priority for loading settings. + + This method overrides the default pydantic-settings loaders to resolve + values in the following order: constructor kwargs, pyproject.toml, + dotenv files, environment variables, and CLI arguments. + """ + _ = (file_secret_settings,) # Allow unused variable to satisfy lint/format + input_args = init_settings() + project_root = Path(input_args.get("project_root") or Path.cwd()) + + return ( + init_settings, + PyprojectTomlConfigSettingsSource( + settings_cls, toml_file=project_root / "pyproject.toml" + ), + dotenv_settings, + env_settings, + CliSettingsSource( + settings_cls, + cli_ignore_unknown_args=True, + cli_parse_args=True, + cli_prefix=settings_cls.model_config.get("cli_prefix", ""), + ), + ) + def __str__(self) -> str: """ Return a concise string representation of the settings. diff --git a/.github/actions/project/publish/action.yml b/tests/__init__.py similarity index 64% rename from .github/actions/project/publish/action.yml rename to tests/__init__.py index 44e3319..16fa815 100644 --- a/.github/actions/project/publish/action.yml +++ b/tests/__init__.py @@ -1,4 +1,3 @@ ---- # Copyright 2026 markurtz # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,12 +11,8 @@ # 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. -name: "Project Publish" -description: "Placeholder action (publishing is handled at component level)" -runs: - using: "composite" - steps: - - name: Project Publish Info - shell: bash - run: |- - echo "[INFO] Project-level publishing is handled via individual component actions (Python, OCI)." +# +# Unless otherwise noted, all files in this directory and its subdirectories +# are licensed under the Apache License, Version 2.0. + +"""Tests package.""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..476f771 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,303 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Unless otherwise noted, all files in this directory and its subdirectories +# are licensed under the Apache License, Version 2.0. + +from __future__ import annotations + +import asyncio +import contextlib +import logging +import os +import shutil +import subprocess +from collections.abc import Generator +from functools import wraps +from pathlib import Path + +import pytest +from loguru import logger + +__all__ = [ + "GitRepoHelper", + "PropagateHandler", + "async_timeout", + "caplog_loguru", + "e2e_git_repo", + "temp_git_repo", +] + + +def pytest_configure(config: pytest.Config) -> None: + """Apply TEST_FILTER env var and default log level for tests.""" + test_filter = os.environ.get("TEST_FILTER") + if test_filter and not config.option.keyword: + config.option.keyword = test_filter + os.environ.setdefault("RUSTARIUM__LOGGING__LEVEL", "ERROR") + + +class PropagateHandler(logging.Handler): + """ + Routes loguru logs to standard logging so that caplog can capture them. + """ + + def emit(self, record: logging.LogRecord) -> None: + """Route loguru record to standard logging.""" + log_logger = logging.getLogger(record.name) + if log_logger.isEnabledFor(record.levelno): + log_logger.handle(record) + + +@pytest.fixture(autouse=True) +def caplog_loguru( + caplog: pytest.LogCaptureFixture, +) -> Generator[pytest.LogCaptureFixture, None, None]: + """ + Hook loguru into pytest's caplog fixture. + + This ensures that assertions like `assert "foo" in caplog.text` work + seamlessly with Loguru output. + """ + import rustarium.logging # noqa: PLC0415 + + logger.remove() + rustarium.logging._state["handler_id"] = None + logger.enable("rustarium") + + logger.add(PropagateHandler(), format="{message}") + yield caplog + logger.remove() + rustarium.logging._state["handler_id"] = None + logger.disable("rustarium") + + +class GitRepoHelper: + """Helper class to manage a temporary git repository for tests.""" + + def __init__(self, path: Path, init: bool = True, initial_commit: bool = False): + self.path = path + if init: + self._init_repo(initial_commit=initial_commit) + + def _init_repo(self, initial_commit: bool = True) -> None: + """Initialize a git repository in the temporary path.""" + try: + subprocess.check_call( + ["git", "init", "-b", "main"], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + subprocess.check_call( + ["git", "init"], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + with contextlib.suppress(subprocess.CalledProcessError): + subprocess.check_call( + ["git", "checkout", "-b", "main"], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + subprocess.check_call( + ["git", "config", "user.name", "Test User"], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.check_call( + ["git", "config", "user.email", "test@example.com"], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + # Create a dummy pyproject.toml for hatchling and maturin tests + pyproject_path = self.path / "pyproject.toml" + pyproject_path.write_text( + '[project]\nname = "test_pkg"\ndynamic = ["version"]\n', encoding="utf-8" + ) + if initial_commit: + self.add("pyproject.toml") + self.commit("Initial commit") + + def branch(self, name: str) -> None: + """Create a branch in the repository.""" + subprocess.check_call( + ["git", "checkout", "-b", name], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def add(self, file: str) -> None: + """Add a file to the repository.""" + subprocess.check_call( + ["git", "add", file], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + @property + def short_sha(self) -> str: + """Get the short SHA of the current commit.""" + return subprocess.check_output( + ["git", "rev-parse", "--short", "HEAD"], + cwd=self.path, + text=True, + stderr=subprocess.DEVNULL, + ).strip() + + def commit(self, message: str, empty: bool = True) -> None: + """Create a commit in the repository.""" + cmd = ["git", "commit", "-m", message] + if empty: + cmd.append("--allow-empty") + subprocess.check_call( + cmd, + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def tag( + self, name: str, annotated: bool = False, message: str | None = None + ) -> None: + """Create a tag in the repository.""" + cmd = ["git", "tag"] + if annotated: + cmd.extend(["-a", name, "-m", message or f"Tag {name}"]) + else: + cmd.append(name) + subprocess.check_call( + cmd, + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def dirty(self, filename: str = "dirty.txt") -> None: + """Make the working directory dirty by creating an untracked file.""" + file_path = self.path / filename + file_path.write_text("dirty") + + def checkout_detached(self) -> None: + """Checkout a detached HEAD state.""" + # Get the current commit hash + commit_hash = subprocess.check_output( + ["git", "rev-parse", "HEAD"], + cwd=self.path, + text=True, + stderr=subprocess.DEVNULL, + ).strip() + subprocess.check_call( + ["git", "checkout", commit_hash], + cwd=self.path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def shallow_clone(self, target_path: Path) -> GitRepoHelper: + """Create a shallow clone of the repository.""" + subprocess.check_call( + ["git", "clone", "--depth", "1", f"file://{self.path}", str(target_path)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + # Re-configure user so commits can be made in the clone if needed + subprocess.check_call( + ["git", "config", "user.name", "Test User"], + cwd=target_path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.check_call( + ["git", "config", "user.email", "test@example.com"], + cwd=target_path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + pyproject_path = self.path / "pyproject.toml" + if pyproject_path.exists(): + shutil.copy2(pyproject_path, target_path / "pyproject.toml") + return GitRepoHelper(target_path, init=False) + + def remove_git_dir(self) -> None: + """Remove the .git directory to simulate a downloaded source archive.""" + git_dir = self.path / ".git" + if git_dir.exists(): + shutil.rmtree(git_dir) + + def setup_state(self, state: str) -> GitRepoHelper: + """Set up the repository to a specific state and return the GitRepoHelper.""" + if state != "clean": + self.commit("First commit") + + if state in { + "lightweight_tag", + "tagged", + "tagged_dirty", + "detached", + "shallow", + "tagged_plus_commit", + "annotated_tag", + }: + annotated = state == "annotated_tag" + self.tag("v1.0.0", annotated=annotated) + + if state == "tagged_plus_commit": + self.commit("Second commit") + + if state in {"dirty", "tagged_dirty"}: + self.dirty() + + if state == "detached": + self.checkout_detached() + + if state == "shallow": + clone_path = self.path.with_name(self.path.name + "_shallow") + return self.shallow_clone(clone_path) + + if state == "no_git": + self.remove_git_dir() + + return self + + +@pytest.fixture +def temp_git_repo(tmp_path: Path) -> GitRepoHelper: + """Yield a temporary git repository helper.""" + return GitRepoHelper(tmp_path) + + +@pytest.fixture +def e2e_git_repo(temp_git_repo: GitRepoHelper) -> GitRepoHelper: + """Yield a temporary git repo helper configured for E2E tests.""" + return temp_git_repo + + +def async_timeout(delay): + def decorator(func): + @wraps(func) + async def new_func(*args, **kwargs): + return await asyncio.wait_for(func(*args, **kwargs), timeout=delay) + + return new_func + + return decorator diff --git a/tests/python/__init__.py b/tests/python/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/python/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/python/integration/test_integration.py b/tests/python/integration/test_integration.py index 653b2a3..19eb362 100644 --- a/tests/python/integration/test_integration.py +++ b/tests/python/integration/test_integration.py @@ -11,11 +11,7 @@ import rustarium.logging as logging_module from rustarium.__main__ import main from rustarium.logging import LoggingSettings, configure_logger -from rustarium.version import ( - __BUILD_METADATA__, - __GIT_METADATA__, - __VERSION_METADATA__, -) +from rustarium.version import __BUILD_METADATA__, __GIT_METADATA__, __VERSION_METADATA__ @pytest.fixture @@ -31,30 +27,20 @@ class TestMain: @pytest.mark.smoke def test_invocation( - self, monkeypatch: pytest.MonkeyPatch, capture_sink: StringIO + self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: """A smoke test ensuring the main entrypoint executes without crashing.""" - original_configure = configure_logger - - def mock_configure(settings: LoggingSettings | None = None) -> None: - if settings is None: - settings = LoggingSettings() - settings.level = "DEBUG" - settings.sink = capture_sink - settings.filter = False - settings.enqueue = False - original_configure(settings) - - monkeypatch.setattr("rustarium.__main__.configure_logger", mock_configure) monkeypatch.setattr(sys, "argv", ["rustarium"]) with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 0 - output = capture_sink.getvalue() - assert "Hello from rustarium" in output - assert "Settings:" in output + captured = capsys.readouterr() + assert ( + "Rustarium: High-performance process orchestrator and sandbox" + in captured.out + ) @pytest.mark.smoke def test_invocation_with_command( diff --git a/tests/python/unit/test_logging.py b/tests/python/unit/test_logging.py index efe8f93..119d176 100644 --- a/tests/python/unit/test_logging.py +++ b/tests/python/unit/test_logging.py @@ -1,16 +1,39 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """Unit tests for the logging module.""" from __future__ import annotations from collections.abc import Generator -from typing import Any +from datetime import datetime +from typing import Any, cast from unittest import mock +from unittest.mock import MagicMock, patch import pytest -from loguru import logger as global_logger from pydantic_settings import BaseSettings -from rustarium.logging import LoggingSettings, configure_logger +from rustarium.logging import ( + LoggingSettings, + _state, + autolog, + configure_logger, +) +from rustarium.logging import ( + logger as global_logger, +) @pytest.fixture(autouse=True) @@ -19,6 +42,7 @@ def reset_logger_fixture() -> Generator[None, None, None]: yield global_logger.remove() global_logger.enable("rustarium") + _state["handler_id"] = None class TestLoggingSettings: @@ -94,3 +118,169 @@ def test_invalid(self) -> None: pytest.raises(ImportError, match="OpenTelemetry is not installed"), ): configure_logger(settings_obj) + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("settings_dict", "mock_otel", "expect_otel"), + [ + ({"enabled": True, "otel_formatting": "disable"}, True, False), + ({"enabled": True, "otel_formatting": "auto"}, True, True), + ({"enabled": True, "otel_formatting": "auto"}, False, False), + ({"enabled": True, "otel_formatting": "enable"}, True, True), + ], + ) + def test_invocation_otel( + self, + settings_dict: dict[str, Any], + mock_otel: bool, + expect_otel: bool, + ) -> None: + """Test configure_logger correctly configures OpenTelemetry formatting.""" + settings = LoggingSettings(**settings_dict) + + with patch("rustarium.logging.logger") as mock_logger: + mock_logger.add.return_value = 42 + if mock_otel: + mock_trace = MagicMock() + mock_span = MagicMock() + mock_context = MagicMock() + mock_context.is_valid = True + mock_context.trace_id = 12345 + mock_context.span_id = 67890 + mock_context.trace_flags = 1 + mock_span.get_span_context.return_value = mock_context + mock_trace.get_current_span.return_value = mock_span + patch_target = "rustarium.logging.opentelemetry_trace" + with patch(patch_target, mock_trace): + configure_logger(settings) + else: + with patch("rustarium.logging.opentelemetry_trace", None): + configure_logger(settings) + + mock_logger.add.assert_called_once() + add_kwargs = mock_logger.add.call_args.kwargs + + if expect_otel: + assert callable(add_kwargs["format"]) + + class MockLevel: + name = "INFO" + + class MockProcess: + id = 1234 + + record_data: dict[str, Any] = { + "time": datetime.now(), + "level": MockLevel(), + "message": "test message {with} ", + "name": "rustarium.test", + "function": "func", + "line": 10, + "extra": {"custom_key": "custom_val"}, + "process": MockProcess(), + } + + # Verify formatter output formatting with trace details + if mock_otel: + with patch("rustarium.logging.opentelemetry_trace", mock_trace): + formatted_log = add_kwargs["format"](record_data) + assert "trace_id" in formatted_log + assert "span_id" in formatted_log + assert "trace_flags" in formatted_log + assert "\\" in formatted_log + assert "{{with}}" in formatted_log + assert "process_id" in formatted_log + else: + assert add_kwargs["format"] == settings.format + + +class TestAutolog: + """Test suite for the autolog decorator.""" + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("use_factory", "exception_level", "should_fail"), + [ + (False, "ERROR", False), + (True, "ERROR", False), + (False, "ERROR", True), + (True, "ERROR", True), + (True, None, True), + (True, "WARNING", True), + ], + ) + def test_invocation( + self, + use_factory: bool, + exception_level: str | None, + should_fail: bool, + ) -> None: + """Test autolog decorator's core logging and wrapping functionality.""" + + def target_func(first_arg: int, second_arg: str) -> str: + if should_fail: + raise ValueError("custom error") + return f"{first_arg}-{second_arg}" + + if use_factory: + decorator = autolog(exception_log_level=exception_level) + wrapped = decorator(target_func) + else: + wrapped = autolog(target_func) + + with patch("rustarium.logging.logger") as mock_logger: + mock_opt_logger = MagicMock() + mock_logger.opt.return_value = mock_opt_logger + + if should_fail: + with pytest.raises(ValueError, match="custom error") as exc_info: + wrapped(42, "hello") + + # Verify entry log + expected_msg = ( + "Calling function " + "'TestAutolog.test_invocation..target_func' " + "with args=(42, 'hello'), kwargs={}" + ) + mock_logger.debug.assert_any_call(expected_msg) + + # Verify exception log + if exception_level is not None or not use_factory: + expected_level = exception_level if use_factory else "ERROR" + if expected_level == "ERROR": + mock_logger.opt.assert_called_once_with( + exception=exc_info.value + ) + mock_opt_logger.error.assert_called_once() + log_args = mock_opt_logger.error.call_args[0] + assert "Exception occurred in function" in log_args[0] + else: + mock_logger.opt.assert_not_called() + mock_logger.log.assert_called_once() + log_args = mock_logger.log.call_args[0] + assert log_args[0] == expected_level + assert "Exception occurred in function" in log_args[1] + else: + mock_logger.opt.assert_not_called() + mock_logger.log.assert_not_called() + else: + result = wrapped(42, "hello") + assert result == "42-hello" + + # Verify entry and exit debug logs + assert mock_logger.debug.call_count == 2 + first_call_args = mock_logger.debug.call_args_list[0][0][0] + assert "Calling function" in first_call_args + assert "target_func" in first_call_args + assert "42" in first_call_args + assert "hello" in first_call_args + + second_call_args = mock_logger.debug.call_args_list[1][0][0] + assert "returned" in second_call_args + assert "42-hello" in second_call_args + + @pytest.mark.sanity + def test_invalid(self) -> None: + """Verify invalid autolog usage raising TypeError.""" + with pytest.raises(TypeError): + cast("Any", autolog)(None, "WARNING") diff --git a/tests/python/unit/test_settings.py b/tests/python/unit/test_settings.py index 6ceadef..7ebcab0 100644 --- a/tests/python/unit/test_settings.py +++ b/tests/python/unit/test_settings.py @@ -1,16 +1,46 @@ +# Copyright 2026 markurtz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """Unit tests for the settings module.""" from __future__ import annotations from pathlib import Path +from typing import Any import pytest from pydantic import ValidationError -from pydantic_settings import BaseSettings +from pydantic_settings import ( + BaseSettings, + CliSettingsSource, + PydanticBaseSettingsSource, + PyprojectTomlConfigSettingsSource, +) from rustarium.settings import Settings +class MockSettingsSource(PydanticBaseSettingsSource): + """Mock settings source for testing customise_sources.""" + + def __call__(self) -> dict[str, Any]: + return {"project_root": Path("/test/tmp")} + + def get_field_value(self, field: Any, field_name: str) -> tuple[Any, str, bool]: + return None, "", False + + class TestSettings: """Test suite for the Settings model.""" @@ -53,6 +83,20 @@ def test_marshalling(self, valid_instances: Settings) -> None: assert recreated_settings.environment == valid_instances.environment assert recreated_settings.project_root == valid_instances.project_root + @pytest.mark.regression + def test_settings_customise_sources(self) -> None: + """Test customization of settings sources.""" + init_source = MockSettingsSource(Settings) + sources = Settings.settings_customise_sources( + Settings, init_source, init_source, init_source, init_source + ) + assert len(sources) == 5 + assert sources[0] is init_source + assert isinstance(sources[1], PyprojectTomlConfigSettingsSource) + assert sources[2] is init_source + assert sources[3] is init_source + assert isinstance(sources[4], CliSettingsSource) + @pytest.mark.smoke def test___str__(self, valid_instances: Settings) -> None: """Test the concise string representation.""" diff --git a/uv.lock b/uv.lock index dc0cd71..a562d2b 100644 --- a/uv.lock +++ b/uv.lock @@ -1305,6 +1305,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" }, ] +[[package]] +name = "gitversioned" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "loguru" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "setuptools" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tstr" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/17/28b6154224946ef69b18daefe46a9f45382abe00b5857106c156bf63d0e2/gitversioned-0.2.0.tar.gz", hash = "sha256:b8e6b9be488eed433d4b13ad2fa4d876e61949651cd80eccbbd487e112a6144a", size = 5486503, upload-time = "2026-05-29T16:50:49.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/03/e24de3c16e3551dd2a3abf049beece7f59e00edacc17d32e1d23333f028a/gitversioned-0.2.0-py3-none-any.whl", hash = "sha256:9ef29f067ed244cfdf0f6ca15445aea350afc345f23643bdd4932b2b099f8ee7", size = 60024, upload-time = "2026-05-29T16:50:47.317Z" }, +] + [[package]] name = "glom" version = "25.12.0" @@ -1331,6 +1350,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, ] +[[package]] +name = "griffelib" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1901,6 +1929,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] +[[package]] +name = "mike" +version = "2.2.0+zensical.0.1.0" +source = { git = "https://github.com/squidfunk/mike.git#2d4ad799442f4592db8ad53b179bfb33db8c69ac" } +dependencies = [ + { name = "jinja2" }, + { name = "pyparsing" }, + { name = "verspec" }, + { name = "zensical" }, +] + [[package]] name = "mkdocs" version = "1.6.1" @@ -1925,6 +1964,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] +[[package]] +name = "mkdocs-autorefs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, +] + [[package]] name = "mkdocs-gen-files" version = "0.6.1" @@ -1952,6 +2005,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, ] +[[package]] +name = "mkdocstrings" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/5d/f888d4d3eb31359b327bc9b17a212d6ef03fe0b0682fbb3fc2cb849fb12b/mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172", size = 100088, upload-time = "2026-04-15T09:16:53.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b", size = 35560, upload-time = "2026-04-15T09:16:51.436Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffelib" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, +] + [[package]] name = "monotable" version = "3.2.0" @@ -3288,16 +3373,16 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] [[package]] @@ -3896,8 +3981,12 @@ name = "rustarium" source = { editable = "." } dependencies = [ { name = "loguru" }, + { name = "packaging" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "setuptools" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tstr" }, { name = "typer" }, ] @@ -3911,13 +4000,17 @@ opentelemetry = [ dev = [ { name = "checkov" }, { name = "detect-secrets" }, + { name = "gitversioned" }, { name = "hatch" }, { name = "mdformat" }, { name = "mdformat-footnote" }, { name = "mdformat-frontmatter" }, { name = "mdformat-gfm" }, { name = "mdformat-gfm-alerts" }, + { name = "mike" }, { name = "mkdocs-gen-files" }, + { name = "mkdocstrings" }, + { name = "mkdocstrings-python" }, { name = "phmdoctest" }, { name = "pip-audit" }, { name = "pytest" }, @@ -3936,10 +4029,14 @@ dev = [ { name = "zensical" }, ] docs = [ + { name = "mike" }, { name = "mkdocs-gen-files" }, + { name = "mkdocstrings" }, + { name = "mkdocstrings-python" }, { name = "zensical" }, ] environment = [ + { name = "gitversioned" }, { name = "hatch" }, ] lint = [ @@ -3973,12 +4070,16 @@ test = [ [package.metadata] requires-dist = [ - { name = "loguru", specifier = ">=0.7.0" }, + { name = "loguru", specifier = "~=0.7" }, { name = "opentelemetry-api", marker = "extra == 'opentelemetry'", specifier = ">=1.0.0" }, { name = "opentelemetry-sdk", marker = "extra == 'opentelemetry'", specifier = ">=1.0.0" }, - { name = "pydantic", specifier = ">=2.0.0" }, - { name = "pydantic-settings", specifier = ">=2.0.0" }, - { name = "typer", specifier = ">=0.12.0" }, + { name = "packaging", specifier = ">=26.0" }, + { name = "pydantic", specifier = "~=2.0" }, + { name = "pydantic-settings", specifier = "~=2.0" }, + { name = "setuptools", specifier = ">=64.0.0" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = "~=2.0" }, + { name = "tstr", specifier = ">=0.4.1" }, + { name = "typer", specifier = ">=0.12" }, ] provides-extras = ["opentelemetry"] @@ -3986,21 +4087,25 @@ provides-extras = ["opentelemetry"] dev = [ { name = "checkov", specifier = ">=3.0,<4" }, { name = "detect-secrets", specifier = ">=1.5,<2" }, + { name = "gitversioned", specifier = ">=0.2.0" }, { name = "hatch", specifier = ">=1.12.0" }, { name = "mdformat", specifier = ">=1.0,<2" }, { name = "mdformat-footnote", specifier = ">=0.1.1,<1" }, { name = "mdformat-frontmatter", specifier = ">=2.0,<3" }, { name = "mdformat-gfm", specifier = ">=1.0,<2" }, { name = "mdformat-gfm-alerts", specifier = ">=1.0.1,<3" }, + { name = "mike", git = "https://github.com/squidfunk/mike.git" }, { name = "mkdocs-gen-files", specifier = ">=0.5.0" }, + { name = "mkdocstrings", specifier = ">=0.24.0" }, + { name = "mkdocstrings-python", specifier = ">=1.9.0" }, { name = "phmdoctest", specifier = ">=1.4,<2" }, { name = "pip-audit", specifier = ">=2.7,<3" }, - { name = "pytest", specifier = ">=9.0,<10" }, - { name = "pytest-asyncio", specifier = ">=1.3,<2" }, - { name = "pytest-cov", specifier = ">=7.1,<8" }, - { name = "pytest-mock", specifier = ">=3.15,<4" }, + { name = "pytest", specifier = ">=9.0.3,<10" }, + { name = "pytest-asyncio", specifier = ">=1.4.0,<2" }, + { name = "pytest-cov", specifier = ">=7.1.0,<8" }, + { name = "pytest-mock", specifier = ">=3.15.1,<4" }, { name = "pytest-xdist", specifier = ">=3.5,<4" }, - { name = "respx", specifier = ">=0.23,<1" }, + { name = "respx", specifier = ">=0.23.1,<1" }, { name = "ruff", specifier = ">=0.15,<1" }, { name = "semgrep", specifier = ">=1.73,<2" }, { name = "taplo" }, @@ -4011,10 +4116,16 @@ dev = [ { name = "zensical", specifier = ">=0.0.40" }, ] docs = [ + { name = "mike", git = "https://github.com/squidfunk/mike.git" }, { name = "mkdocs-gen-files", specifier = ">=0.5.0" }, + { name = "mkdocstrings", specifier = ">=0.24.0" }, + { name = "mkdocstrings-python", specifier = ">=1.9.0" }, { name = "zensical", specifier = ">=0.0.40" }, ] -environment = [{ name = "hatch", specifier = ">=1.12.0" }] +environment = [ + { name = "gitversioned", specifier = ">=0.2.0" }, + { name = "hatch", specifier = ">=1.12.0" }, +] lint = [ { name = "mdformat", specifier = ">=1.0,<2" }, { name = "mdformat-footnote", specifier = ">=0.1.1,<1" }, @@ -4036,12 +4147,12 @@ security = [ { name = "semgrep", specifier = ">=1.73,<2" }, ] test = [ - { name = "pytest", specifier = ">=9.0,<10" }, - { name = "pytest-asyncio", specifier = ">=1.3,<2" }, - { name = "pytest-cov", specifier = ">=7.1,<8" }, - { name = "pytest-mock", specifier = ">=3.15,<4" }, + { name = "pytest", specifier = ">=9.0.3,<10" }, + { name = "pytest-asyncio", specifier = ">=1.4.0,<2" }, + { name = "pytest-cov", specifier = ">=7.1.0,<8" }, + { name = "pytest-mock", specifier = ">=3.15.1,<4" }, { name = "pytest-xdist", specifier = ">=3.5,<4" }, - { name = "respx", specifier = ">=0.23,<1" }, + { name = "respx", specifier = ">=0.23.1,<1" }, ] [[package]] @@ -4460,6 +4571,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/96/9666c8ba2e7695e804b2b2129e45202320d2a8b5e2c9f6e5e837a61708f5/trove_classifiers-2026.5.20.19-py3-none-any.whl", hash = "sha256:7a173916960d0635fcbf610550d2c27bcc9125164d6f397adf46fc1ef6455c7c", size = 14224, upload-time = "2026-05-20T19:17:42.949Z" }, ] +[[package]] +name = "tstr" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/40/88e526174c65fe84552c5b920b0b7ec0787c52148106932cb72020561080/tstr-0.4.1.tar.gz", hash = "sha256:bad326e07fe239eb4c63635964a6de9d55ff40ab223b04c6361b01c3b709523d", size = 47180, upload-time = "2026-04-11T22:49:36.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/93/07444ed2e23e5378da5e0468e26626955508a587bcfb2ffa95d1398582e0/tstr-0.4.1-py3-none-any.whl", hash = "sha256:2a7b387855fddb24e57edaaf347c37f6bde5c2673fbc851e576ee802d56c6bd3", size = 19839, upload-time = "2026-04-11T22:49:35.089Z" }, +] + [[package]] name = "ty" version = "0.0.38" @@ -4631,6 +4751,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" }, ] +[[package]] +name = "verspec" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/44/8126f9f0c44319b2efc65feaad589cadef4d77ece200ae3c9133d58464d0/verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e", size = 27123, upload-time = "2020-11-30T02:24:09.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31", size = 19640, upload-time = "2020-11-30T02:24:08.387Z" }, +] + [[package]] name = "virtualenv" version = "21.3.3" diff --git a/zensical.toml b/zensical.toml index e93b0fa..a7f0380 100644 --- a/zensical.toml +++ b/zensical.toml @@ -19,19 +19,28 @@ site_description = "A high-performance, telemetrized process orchestrator and sa site_author = "markurtz" repo_url = "https://github.com/markurtz/rustarium" repo_name = "markurtz/rustarium" -nav = [ - "index.md", - {"Getting Started" = ["getting-started/index.md", "getting-started/installation.md", "getting-started/quickstart.md"]}, - {Guides = ["guides/index.md", "guides/github-workflows.md"]}, - {Examples = ["examples/index.md"]}, - {Reference = ["reference/index.md", "reference/cli.md", "reference/python_api.md", "reference/rust_api.md", "reference/python_coverage.md", "reference/rust_coverage.md"]}, - {Community = ["community/index.md", "community/agents.md", "community/code-of-conduct.md", "community/contributing.md", "community/developing.md", "community/security.md", "community/support.md"]}, -] + +[project.extra.version] +provider = "mike" +default = "latest" + [project.theme] variant = "modern" features = ["navigation.indexes"] +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +toggle.icon = "lucide/sun" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + [project.plugins.macros] module_name = "docs/scripts/macros"