diff --git a/.github/workflows/ci-quality.yml b/.github/workflows/ci-quality.yml
index f8083f007..243dac9de 100644
--- a/.github/workflows/ci-quality.yml
+++ b/.github/workflows/ci-quality.yml
@@ -21,6 +21,19 @@ on:
- 'uv.lock'
- '.ruff.toml'
- '.github/workflows/ci-quality.yml'
+ # pull_request_target is needed only for the auto-format-suggest job to call
+ # the PR Reviews API on fork PRs. All other jobs gate on event_name to avoid
+ # double execution.
+ pull_request_target:
+ branches: [ main, develop ]
+ paths:
+ - 'src/**'
+ - 'tests/**'
+ - 'pyproject.toml'
+ - 'requirements*.txt'
+ - 'uv.lock'
+ - '.ruff.toml'
+ - '.github/workflows/ci-quality.yml'
permissions:
contents: read
@@ -32,6 +45,8 @@ jobs:
quality-check:
name: Quality Standards
+ # Skip on pull_request_target — only auto-format-suggest needs that event.
+ if: github.event_name != 'pull_request_target'
runs-on: ubuntu-latest
needs: config
permissions:
@@ -65,6 +80,8 @@ jobs:
setup-cache:
name: Setup Cache
+ # Runs on both pull_request and pull_request_target — auto-format-suggest
+ # depends on it and needs to run on pull_request_target for fork PRs.
needs: config
uses: ./.github/workflows/cache-management.yml
with:
@@ -73,8 +90,10 @@ jobs:
python-version: ${{ needs.config.outputs.default-python-version }}
auto-format:
- name: Auto-Format Code
- if: github.event_name == 'pull_request'
+ name: Auto-Format Code
+ # Same-repo PRs only: GITHUB_TOKEN can push directly to the branch.
+ # Fork PRs are handled by the auto-format-suggest job below.
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
needs: [config, setup-cache]
permissions:
@@ -108,8 +127,48 @@ jobs:
git push
fi
+ auto-format-suggest:
+ name: Auto-Format Suggestions
+ # Fork PRs: post formatted changes as PR review suggestions for the
+ # contributor to apply via the GitHub UI's "Commit suggestion" button.
+ # Uses pull_request_target so the GITHUB_TOKEN can call the PR Reviews API
+ # (POST /pulls/{id}/reviews) — pull_request fork-PR tokens cannot.
+ # The workflow file is evaluated at the base ref (safe); only the PR head
+ # code is checked out into a non-elevated working tree for formatting.
+ if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
+ runs-on: ubuntu-latest
+ needs: [config, setup-cache]
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - name: Checkout PR head
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ # No token here — formatter does not need write access; this prevents
+ # untrusted PR code from authenticating with elevated permissions.
+ persist-credentials: false
+
+ - name: Setup UV with cache
+ uses: ./.github/actions/setup-uv-cached
+ with:
+ cache-key: ${{ needs.setup-cache.outputs.cache-key }}
+ fail-on-cache-miss: false
+
+ - name: Run formatter
+ run: make format-fix
+
+ - name: Post review suggestions
+ uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # v1.24.0
+ with:
+ tool_name: ruff
+ level: warning
+ fail_on_error: false
+
lint-ruff:
name: Ruff (Code Quality)
+ if: github.event_name != 'pull_request_target'
runs-on: ubuntu-latest
needs: [config, setup-cache]
permissions:
@@ -129,6 +188,7 @@ jobs:
lint-ruff-optional:
name: Ruff (Extended Checks)
+ if: github.event_name != 'pull_request_target'
runs-on: ubuntu-latest
needs: [config, setup-cache, lint-ruff]
permissions:
@@ -149,6 +209,7 @@ jobs:
lint-pyright:
name: Type Checking (pyright)
+ if: github.event_name != 'pull_request_target'
runs-on: ubuntu-latest
needs: [config, setup-cache, lint-ruff]
permissions:
@@ -169,6 +230,7 @@ jobs:
arch-validation:
name: Architecture Validation
+ if: github.event_name != 'pull_request_target'
runs-on: ubuntu-latest
needs: [config, setup-cache, lint-ruff]
permissions:
diff --git a/.github/workflows/security-code.yml b/.github/workflows/security-code.yml
index a38a4d99b..41fb58035 100644
--- a/.github/workflows/security-code.yml
+++ b/.github/workflows/security-code.yml
@@ -118,12 +118,17 @@ jobs:
# in their PyPI METADATA. The compound string is treated as a single
# non-allowlisted license. Each exemption below has been verified by reading
# the package's METADATA license / License-Expression directly:
- # werkzeug BSD-3-Clause AND LicenseRef-scancode-unknown-license-reference -> BSD-3-Clause
- # lark MIT AND MPL-2.0 -> MIT (METADATA: License: MIT)
- # mcp MIT AND Python-2.0 -> MIT (METADATA: License: MIT)
- # rfc3987-syntax Apache-2.0 AND GPL-1.0-or-later AND MIT -> MIT (METADATA: License-Expression: MIT)
- # wcwidth MIT AND HPND-Markus-Kuhn -> MIT (METADATA: License-Expression: MIT)
- allow-dependencies-licenses: 'pkg:pypi/werkzeug, pkg:githubactions/trufflesecurity/trufflehog, pkg:pypi/lark, pkg:pypi/mcp, pkg:pypi/rfc3987-syntax, pkg:pypi/wcwidth'
+ # werkzeug BSD-3-Clause AND LicenseRef-scancode-unknown-license-reference -> BSD-3-Clause
+ # lark MIT AND MPL-2.0 -> MIT (METADATA: License: MIT)
+ # mcp MIT AND Python-2.0 -> MIT (METADATA: License: MIT)
+ # rfc3987-syntax Apache-2.0 AND GPL-1.0-or-later AND MIT -> MIT (METADATA: License-Expression: MIT)
+ # wcwidth MIT AND HPND-Markus-Kuhn -> MIT (METADATA: License-Expression: MIT)
+ # lxml BSD-3-Clause AND GPL-1.0-or-later -> BSD-3-Clause (METADATA: License: BSD-3-Clause; GPL applies to a single legacy source file)
+ # protobuf BSD-3-Clause AND LicenseRef-scancode-protobuf -> 3-Clause BSD (METADATA License text)
+ # pywin32 PSF-2.0 -> PSF-2.0 (permissive, equivalent to BSD)
+ # cyclonedx-python-lib no detected license -> Apache-2.0 (METADATA: License: Apache-2.0)
+ # fastapi no detected license -> MIT (METADATA: License-Expression: MIT)
+ allow-dependencies-licenses: 'pkg:pypi/werkzeug, pkg:githubactions/trufflesecurity/trufflehog, pkg:pypi/lark, pkg:pypi/mcp, pkg:pypi/rfc3987-syntax, pkg:pypi/wcwidth, pkg:pypi/lxml, pkg:pypi/protobuf, pkg:pypi/pywin32, pkg:pypi/cyclonedx-python-lib, pkg:pypi/fastapi'
codeql-analysis:
name: CodeQL Analysis
diff --git a/.github/workflows/validate-workflows.yml b/.github/workflows/validate-workflows.yml
index 8ad0f71dd..6523d8cf2 100644
--- a/.github/workflows/validate-workflows.yml
+++ b/.github/workflows/validate-workflows.yml
@@ -69,4 +69,4 @@ jobs:
- name: Validate shell scripts with shellcheck
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
with:
- ignore_paths: '.venv .git __pycache__ node_modules'
+ ignore_paths: '.venv .git __pycache__ node_modules k8s'
diff --git a/.gitignore b/.gitignore
index 9dfe59b4d..90f4995ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,8 @@ requirements-dev.txt
work/
# Test autogenerated files
-tests/onaws/run_templates*
+tests/providers/aws/live/run_templates/
+tests/providers/aws/live/run_templates/**
# Desloppify scan artifacts
.desloppify/
diff --git a/.trivyignore b/.trivyignore
index 19fe66b57..98f8ede74 100644
--- a/.trivyignore
+++ b/.trivyignore
@@ -16,3 +16,9 @@ __pycache__/**
htmlcov/**
.tox/**
.mypy_cache/**
+
+# CVE exceptions for transitive CI/dev-only dependencies with no upstream patch
+# nltk 3.9.4 — CVE-2026-54293 (HIGH). Pulled in transitively by `safety` (CI-only
+# security scanner). No fixed version available upstream as of 2026-06-17. Not
+# present in runtime install. Re-evaluate when nltk publishes a patched release.
+CVE-2026-54293
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44249cb97..e7ea52a97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,36 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## Unreleased
+### Added
+
+- New `[aws]` extra (alias for AWS deps currently in core). The canonical install
+ command going forward is `pip install orb-py[aws]`. This continues to work
+ unchanged when AWS deps move out of core in a future major version.
+- New `[all-providers]` meta-extra: pulls in all currently implemented providers.
+- New `[monitoring-aws]` extra: AWS-specific OpenTelemetry boto instrumentation (previously bundled inside `[monitoring]`).
+- New `[test-aws]` extra: moto + response-mocking deps for AWS test suites.
+- Architecture test `test_boto3_leak_detection.py`: asserts boto3/botocore are never imported outside `providers/aws/` except by guarded backward-compat shims.
+- Unit tests `test_no_provider_install.py`: verifies ORB core modules boot cleanly when AWS deps are absent.
+
+### Changed
+
+- `[monitoring]` extra no longer includes `opentelemetry-instrumentation-boto` (use `[monitoring-aws]`).
+- `[all]` extra now includes `[all-providers]` so `pip install orb-py[all]` still pulls everything.
+- `[dev]` shim extra now includes `[all-providers]` for full local development.
+- Three module-level import sites guarded with `try/except ImportError` so ORB core boots when AWS extras are absent:
+ `config/schemas/cleanup_schema.py`, `infrastructure/storage/registration.py`,
+ `providers/registration.py` (deprecated shims).
+- `tests/conftest.py`: bare `import boto3` replaced with guarded `AWS_AVAILABLE` flag; AWS provider tests auto-skip when `[aws]` extra is absent.
+
+### Notes
+
+- `boto3` + `botocore` **remain in core** `[project.dependencies]` for now to
+ preserve backward compatibility — `pip install orb-py` still works unchanged.
+- When the second provider (Azure / GCP / OCI) lands, AWS deps will move from
+ core to the `[aws]` extra and a major-version bump will signal the breaking
+ change. Operators who use `pip install orb-py[aws]` today will see no
+ behavior change at that cutover.
+
[Compare with latest](https://github.com/awslabs/open-resource-broker/compare/v0.1.0rc0...HEAD)
### Features
diff --git a/README.md b/README.md
index b36233765..40d970c16 100644
--- a/README.md
+++ b/README.md
@@ -83,12 +83,36 @@ Get ORB installed and configured for your environment.
Installation
-### Standard install
+### Standard install (core only — no provider)
```bash
pip install orb-py
```
+ORB boots cleanly with no provider registered. Any command that needs a provider
+will return a clear "no provider configured" error rather than an ImportError.
+
+### Per-provider install
+
+```bash
+pip install "orb-py[aws]" # AWS provider (boto3 + botocore)
+pip install "orb-py[aws,cli]" # AWS provider + colored CLI output
+pip install "orb-py[aws,api]" # AWS provider + REST API server
+pip install "orb-py[monitoring-aws]" # AWS provider + full monitoring stack
+pip install "orb-py[all]" # All providers + all features
+```
+
+### Provider extras matrix
+
+| Use case | Install command |
+|----------|----------------|
+| Core only (no provider) | `pip install orb-py` |
+| AWS operator | `pip install "orb-py[aws]"` |
+| AWS + colored CLI | `pip install "orb-py[aws,cli]"` |
+| AWS + REST API | `pip install "orb-py[aws,api]"` |
+| AWS + monitoring | `pip install "orb-py[monitoring-aws]"` |
+| Full (all providers + features) | `pip install "orb-py[all]"` |
+
### With colored CLI output
```bash
diff --git a/config/aws_templates.json b/config/aws_templates.json
index 5b491d626..2b1b80706 100644
--- a/config/aws_templates.json
+++ b/config/aws_templates.json
@@ -3,7 +3,7 @@
"templates": [
{
"templateId": "EC2Fleet-Instant-OnDemand",
- "maxNumber": 10,
+ "maxNumber": 100,
"fleetType": "instant",
"vmTypes": {
"t3.medium": 2,
@@ -22,7 +22,7 @@
},
{
"templateId": "EC2Fleet-Instant-Spot",
- "maxNumber": 10,
+ "maxNumber": 100,
"fleetType": "instant",
"vmTypes": {
"t3.medium": 2,
@@ -42,7 +42,7 @@
},
{
"templateId": "EC2Fleet-Instant-Mixed",
- "maxNumber": 10,
+ "maxNumber": 100,
"fleetType": "instant",
"vmTypes": {
"t3.medium": 2,
@@ -63,7 +63,7 @@
},
{
"templateId": "EC2Fleet-Request-OnDemand",
- "maxNumber": 15,
+ "maxNumber": 100,
"fleetType": "request",
"vmTypes": {
"t3.medium": 2,
@@ -82,7 +82,7 @@
},
{
"templateId": "EC2Fleet-Request-Spot",
- "maxNumber": 20,
+ "maxNumber": 100,
"fleetType": "request",
"vmTypes": {
"t3.medium": 2,
@@ -102,7 +102,7 @@
},
{
"templateId": "EC2Fleet-Request-Mixed",
- "maxNumber": 25,
+ "maxNumber": 100,
"fleetType": "request",
"vmTypes": {
"t3.medium": 2,
@@ -125,7 +125,7 @@
},
{
"templateId": "EC2Fleet-Maintain-OnDemand",
- "maxNumber": 12,
+ "maxNumber": 100,
"fleetType": "maintain",
"vmTypes": {
"t3.medium": 2,
@@ -144,7 +144,7 @@
},
{
"templateId": "EC2Fleet-Maintain-Spot",
- "maxNumber": 30,
+ "maxNumber": 100,
"fleetType": "maintain",
"vmTypes": {
"t3.medium": 2,
@@ -164,7 +164,7 @@
},
{
"templateId": "EC2Fleet-Maintain-Mixed",
- "maxNumber": 50,
+ "maxNumber": 100,
"fleetType": "maintain",
"vmTypes": {
"t3.medium": 2,
@@ -187,7 +187,7 @@
},
{
"templateId": "SpotFleet-Request-LowestPrice",
- "maxNumber": 20,
+ "maxNumber": 100,
"fleetType": "request",
"vmTypes": {
"t3.medium": 2,
@@ -208,7 +208,7 @@
},
{
"templateId": "SpotFleet-Request-Diversified",
- "maxNumber": 25,
+ "maxNumber": 100,
"fleetType": "request",
"vmTypes": {
"t3.medium": 2,
@@ -229,7 +229,7 @@
},
{
"templateId": "SpotFleet-Request-CapacityOptimized",
- "maxNumber": 30,
+ "maxNumber": 100,
"fleetType": "request",
"vmTypes": {
"t3.medium": 2,
@@ -250,7 +250,7 @@
},
{
"templateId": "SpotFleet-Maintain-LowestPrice",
- "maxNumber": 15,
+ "maxNumber": 100,
"fleetType": "maintain",
"vmTypes": {
"t3.medium": 2,
@@ -271,7 +271,7 @@
},
{
"templateId": "SpotFleet-Maintain-Diversified",
- "maxNumber": 20,
+ "maxNumber": 100,
"fleetType": "maintain",
"vmTypes": {
"t3.medium": 2,
@@ -292,7 +292,7 @@
},
{
"templateId": "SpotFleet-Maintain-CapacityOptimized",
- "maxNumber": 25,
+ "maxNumber": 100,
"fleetType": "maintain",
"vmTypes": {
"t3.medium": 2,
@@ -313,7 +313,7 @@
},
{
"templateId": "ASG-OnDemand",
- "maxNumber": 15,
+ "maxNumber": 100,
"vmTypes": {
"t3.medium": 2,
"t3.xlarge": 4
@@ -331,7 +331,7 @@
},
{
"templateId": "ASG-Spot",
- "maxNumber": 20,
+ "maxNumber": 100,
"vmTypes": {
"t3.medium": 2,
"t3.xlarge": 4
@@ -350,7 +350,7 @@
},
{
"templateId": "ASG-Mixed",
- "maxNumber": 25,
+ "maxNumber": 100,
"vmTypes": {
"t3.medium": 2,
"t3.large": 2,
@@ -370,7 +370,7 @@
},
{
"templateId": "RunInstances-OnDemand",
- "maxNumber": 5,
+ "maxNumber": 100,
"vmTypes": {
"t3.medium": 2
},
@@ -387,7 +387,7 @@
},
{
"templateId": "RunInstances-Spot",
- "maxNumber": 10,
+ "maxNumber": 100,
"vmTypes": {
"t3.medium": 2
},
diff --git a/dev-tools/testing/run_tests.py b/dev-tools/testing/run_tests.py
index ef9591959..f9827ee20 100755
--- a/dev-tools/testing/run_tests.py
+++ b/dev-tools/testing/run_tests.py
@@ -153,7 +153,7 @@ def main():
markers.append("e2e")
if args.onaws:
- test_paths.append("tests/onaws")
+ test_paths.append("tests/providers/aws/live")
markers.append("aws")
if args.fast:
diff --git a/docs/root/architecture/clean_architecture.md b/docs/root/architecture/clean_architecture.md
index f77f1916e..78659533f 100644
--- a/docs/root/architecture/clean_architecture.md
+++ b/docs/root/architecture/clean_architecture.md
@@ -434,4 +434,10 @@ class TemplateRepositoryImpl(TemplateRepository):
Unit Tests (Domain + Application)
```
+## Adding a New Provider
+
+The provider layer is the primary extensibility surface for new cloud platforms. For a step-by-step guide covering the mandatory glue points and all extension point registries, see:
+
+- [Adding a Provider](../developer_guide/adding_a_provider.md)
+
This Clean Architecture implementation ensures that the Open Resource Broker maintains clear separation of concerns, high testability, and flexibility for future changes while adhering to established software engineering principles.
diff --git a/docs/root/developer_guide/adding_a_provider.md b/docs/root/developer_guide/adding_a_provider.md
new file mode 100644
index 000000000..ec599503f
--- /dev/null
+++ b/docs/root/developer_guide/adding_a_provider.md
@@ -0,0 +1,584 @@
+# Adding a Provider
+
+This guide walks through adding a new cloud provider (such as Azure, GCP, or OCI) to the Open Resource Broker. It is aimed at developers who are already familiar with the overall architecture and want a concrete checklist of required steps and extension points.
+
+## Prerequisites
+
+Before reading this guide, review:
+
+- [Clean Architecture](../architecture/clean_architecture.md) — layer boundaries and dependency rules
+- [Strategy Pattern](../patterns/strategy_pattern.md) — how provider strategies are structured
+- [Ports and Adapters](../patterns/ports_and_adapters.md) — how registries decouple providers from shared infrastructure
+
+## Overview
+
+ORB's provider system is built around an extension-point model: all provider-specific behaviour is registered through a set of dedicated registries at startup. Shared infrastructure (the CLI, the scheduler, the REST API, the DI container) never imports provider packages directly; instead it queries registries that were populated during bootstrap.
+
+The goal of this model is that adding a new provider should touch exactly:
+
+- `src/orb/providers//` — your provider package (all provider logic lives here)
+- Three glue points in shared code: an enum entry, a registration call, and a bootstrap block
+
+The AWS provider is the canonical reference implementation. Refer to `src/orb/providers/aws/registration.py` when you need to see a working example of every registration call described below.
+
+### Provider package layout
+
+Mirror the AWS structure:
+
+```
+src/orb/providers//
+ __init__.py
+ registration.py # All registration functions for this provider
+ strategy/
+ _provider_strategy.py
+ cli/
+ _cli_spec.py
+ configuration/
+ config.py
+ template_extension.py
+ domain/
+ template/
+ _template_aggregate.py
+ _template_dto_config.py
+ scheduler/
+ hostfactory_field_mapping.py
+ auth/
+ _auth_strategy.py
+ defaults_loader.py
+```
+
+## Mandatory steps
+
+Complete these steps in order. Each one has a corresponding section in the extension points reference below.
+
+### 1. Create the provider package
+
+Create `src/orb/providers//` following the layout above. The strategy class must extend `BaseProviderStrategy` from `orb.providers.base.strategy.base_provider_strategy`.
+
+### 2. Add an enum entry
+
+Add your provider to `ProviderType` in `src/orb/domain/base/provider_interfaces.py`:
+
+```python
+class ProviderType(str, Enum):
+ AWS = "aws"
+ AZURE = "azure" # new entry
+```
+
+Use a short, lowercase string that matches the string keys used in all registry calls.
+
+### 3. Add a registration call in `providers/registration.py`
+
+Add your provider to the two functions in `src/orb/providers/registration.py`:
+
+```python
+def register_all_provider_cli_specs() -> None:
+ from orb.domain.base.ports.provider_cli_spec_port import CLISpecRegistry
+ from orb.providers.azure.cli.azure_cli_spec import AzureCLISpec
+
+ if CLISpecRegistry.get("azure") is None:
+ CLISpecRegistry.register("azure", AzureCLISpec())
+
+
+def register_all_provider_types() -> None:
+ from orb.providers.registry import get_provider_registry
+
+ registry = get_provider_registry()
+
+ from orb.providers.aws.registration import register_aws_provider
+ register_aws_provider(registry)
+
+ from orb.providers.azure.registration import register_azure_provider
+ register_azure_provider(registry)
+```
+
+### 4. Add a bootstrap block in `bootstrap/provider_services.py`
+
+Add a `find_spec`-guarded block to `_register_provider_utility_services` in `src/orb/bootstrap/provider_services.py`. Use the same pattern as the AWS block immediately above it:
+
+```python
+if importlib.util.find_spec("orb.providers.azure"):
+ try:
+ from orb.providers.azure.registration import register_azure_services_with_di
+ register_azure_services_with_di(container)
+ except Exception as e:
+ logger.warning("Failed to register Azure utility services: %s", str(e))
+```
+
+The `find_spec` guard means the rest of ORB still runs when the provider package is not installed (useful for minimal deployments and test environments that exclude a specific provider's SDK).
+
+### 5. Add SDK dependencies
+
+Add your cloud provider SDK to `pyproject.toml` and regenerate the lockfile:
+
+```toml
+[project.optional-dependencies]
+azure = ["azure-mgmt-compute>=30.0", "azure-identity>=1.15"]
+```
+
+Then run `uv lock` to update `uv.lock`.
+
+---
+
+## Extension points reference
+
+Each registry is a class-level singleton. Register during startup; never register lazily inside a request handler.
+
+### ProviderRegistry
+
+**Location:** `src/orb/providers/registry/provider_registry.py`
+
+**What it does:** The central strategy factory. When a request arrives for a named provider, `ProviderRegistry` calls your `strategy_factory` to create the strategy instance and `config_factory` to parse configuration data.
+
+**When to register:** In your `register__provider(registry)` function called from `providers/registration.py`.
+
+**How to register:**
+
+```python
+# src/orb/providers/azure/registration.py
+
+def register_azure_provider(registry=None) -> None:
+ from orb.providers.registry import get_provider_registry
+ from orb.providers.azure.strategy.azure_provider_strategy import AzureProviderStrategy
+
+ if registry is None:
+ registry = get_provider_registry()
+
+ registry.register_provider(
+ provider_type="azure",
+ strategy_factory=create_azure_strategy,
+ config_factory=create_azure_config,
+ resolver_factory=create_azure_resolver, # return None if not needed
+ validator_factory=create_azure_validator, # return None if not needed
+ strategy_class=AzureProviderStrategy,
+ default_api=_load_azure_default_api(), # reads from azure_defaults.json
+ )
+```
+
+**AWS reference:** `register_aws_provider` in `src/orb/providers/aws/registration.py`.
+
+---
+
+### CLISpecRegistry + ProviderCLISpecPort
+
+**Location:** `src/orb/domain/base/ports/provider_cli_spec_port.py`
+
+**What it does:** Supplies provider-specific CLI argument definitions, input validation logic, field extraction from parsed arguments, and display formatting. The shared `cli/args.py` and `interface/init_command_handler.py` iterate over `CLISpecRegistry.all()` rather than hard-coding provider flags.
+
+**When to register:** In `register_all_provider_cli_specs()` in `providers/registration.py`. This function is called before application context exists, so keep it lightweight — no network calls, no DI container access.
+
+**How to register:**
+
+```python
+from orb.domain.base.ports.provider_cli_spec_port import CLISpecRegistry
+from orb.providers.azure.cli.azure_cli_spec import AzureCLISpec
+
+CLISpecRegistry.register("azure", AzureCLISpec())
+```
+
+Implement `ProviderCLISpecPort` on your spec class:
+
+```python
+class AzureCLISpec:
+ def add_arguments(self, parser) -> None:
+ parser.add_argument("--azure-subscription-id", help="Azure subscription ID")
+ parser.add_argument("--azure-resource-group", help="Azure resource group")
+
+ def validate(self, args) -> list[str]:
+ errors = []
+ if not getattr(args, "azure_subscription_id", None):
+ errors.append("--azure-subscription-id is required for Azure providers")
+ return errors
+
+ def extract_fields(self, args) -> dict:
+ return {
+ "subscription_id": args.azure_subscription_id,
+ "resource_group": getattr(args, "azure_resource_group", None),
+ }
+```
+
+**AWS reference:** `src/orb/providers/aws/cli/aws_cli_spec.py`.
+
+---
+
+### TemplateExtensionRegistry
+
+**Location:** `src/orb/domain/template/extensions.py`
+
+**What it does:** Holds a typed Pydantic model class for each provider's template configuration. When `TemplateDTO.from_domain` serialises a template, it calls `TemplateExtensionRegistry.get_extension_class(provider_type)` to obtain and populate the `provider_config` field. This replaces ad-hoc `metadata` dict keys and `getattr(template, "validate_")` dynamic dispatch.
+
+**When to register:** In `register__extensions()`, called from `initialize__provider()` during startup.
+
+**How to register:**
+
+```python
+from orb.domain.template.extensions import TemplateExtensionRegistry
+from orb.providers.azure.configuration.template_extension import AzureTemplateExtensionConfig
+
+TemplateExtensionRegistry.register_extension("azure", AzureTemplateExtensionConfig)
+```
+
+`AzureTemplateExtensionConfig` should be a Pydantic `BaseModel` with `get_provider_type()` returning `"azure"` and a `to_template_defaults()` method returning a `dict` of default values:
+
+```python
+from pydantic import BaseModel, Field
+
+class AzureTemplateExtensionConfig(BaseModel):
+ vm_size: str = Field("Standard_D2s_v3", description="Azure VM size")
+ location: str = Field("eastus", description="Azure region")
+ resource_group: str = Field("", description="Azure resource group")
+
+ def get_provider_type(self) -> str:
+ return "azure"
+
+ def to_template_defaults(self) -> dict:
+ return self.model_dump()
+```
+
+**AWS reference:** `src/orb/providers/aws/configuration/template_extension.py` and the `register_aws_extensions` function in `src/orb/providers/aws/registration.py`.
+
+---
+
+### AuthRegistry
+
+**Location:** `src/orb/infrastructure/auth/registry.py`
+
+**What it does:** Maps auth strategy names (strings like `"iam"`, `"cognito"`) to auth strategy classes. The REST server calls `AuthRegistry.get_strategy(name, **config)` rather than dispatching through `if/elif` chains. Register all auth strategies your provider supports.
+
+**When to register:** In `register__auth_strategies()`, called from `initialize__provider()` during startup.
+
+**How to register:**
+
+```python
+from orb.infrastructure.auth.registry import get_auth_registry
+
+registry = get_auth_registry()
+
+if not registry.is_registered("azure_ad"):
+ from orb.providers.azure.auth.azure_ad_strategy import AzureADAuthStrategy
+ registry.register_strategy("azure_ad", AzureADAuthStrategy)
+
+if not registry.is_registered("managed_identity"):
+ from orb.providers.azure.auth.managed_identity_strategy import ManagedIdentityAuthStrategy
+ registry.register_strategy("managed_identity", ManagedIdentityAuthStrategy)
+```
+
+**AWS reference:** `register_aws_auth_strategies` in `src/orb/providers/aws/registration.py`.
+
+---
+
+### FieldMappingRegistry
+
+**Location:** `src/orb/infrastructure/scheduler/hostfactory/field_mapping_registry.py`
+
+**What it does:** Holds a per-provider `FieldMappingPort` adapter. The HostFactory scheduler calls `FieldMappingRegistry.get(provider_type)` to translate IBM Spectrum Symphony camelCase field names to the provider's snake_case equivalents, apply provider-specific defaults, and resolve CPU/RAM values from a provider-specific instance type catalogue.
+
+**When to register:** In `initialize__provider()`, after other registrations.
+
+**How to register:**
+
+```python
+from orb.infrastructure.scheduler.hostfactory.field_mapping_registry import FieldMappingRegistry
+from orb.providers.azure.scheduler.hostfactory_field_mapping import AzureFieldMapping
+
+FieldMappingRegistry.register("azure", AzureFieldMapping())
+```
+
+Implement `FieldMappingPort` in your mapping class. The two critical methods are `map_fields(raw: dict) -> dict` (camelCase-to-snake_case translation + provider defaults) and `resolve_cpu_ram(vm_size: str) -> tuple[int, int]` (returns `(cpu_count, ram_mb)` from your provider's instance catalogue).
+
+**AWS reference:** `src/orb/providers/aws/scheduler/hostfactory_field_mapping.py` and `register_aws_provider` in `src/orb/providers/aws/registration.py` (the `FieldMappingRegistry.register` call near the end of `initialize_aws_provider`).
+
+---
+
+### DefaultsLoaderRegistry
+
+**Location:** `src/orb/providers/registry/defaults_loader_registry.py`
+
+**What it does:** Holds a per-provider `ProviderDefaultsLoaderPort` that loads a provider's defaults JSON file. The template defaults service calls this registry to populate provider-specific default values rather than hard-coding file paths for each provider.
+
+**When to register:** In `initialize__provider()`.
+
+**How to register:**
+
+```python
+from orb.providers.registry.defaults_loader_registry import DefaultsLoaderRegistry
+from orb.providers.azure.defaults_loader import AzureDefaultsLoader
+
+DefaultsLoaderRegistry.register("azure", AzureDefaultsLoader())
+```
+
+`AzureDefaultsLoader` implements `ProviderDefaultsLoaderPort` and typically reads from a JSON file bundled alongside your provider package (e.g. `src/orb/providers/azure/config/azure_defaults.json`).
+
+**AWS reference:** `src/orb/providers/aws/defaults_loader.py` and the `DefaultsLoaderRegistry.register` call in `initialize_aws_provider`.
+
+---
+
+### TemplateAdapterPort
+
+**Location:** `src/orb/domain/base/ports/template_adapter_port.py`
+
+**What it does:** Resolves provider-specific template fields that require a live API call (for example, looking up an AMI by name on AWS, or resolving an image reference on Azure). Registered in the DI container as a singleton, not in a class-level registry.
+
+**When to register:** In `register__services_with_di(container)`, called from the `find_spec`-guarded block in `bootstrap/provider_services.py`.
+
+**How to register:**
+
+```python
+from orb.domain.base.ports.template_adapter_port import TemplateAdapterPort
+
+def create_azure_template_adapter(c):
+ from orb.providers.azure.infrastructure.adapters.template_adapter import AzureTemplateAdapter
+ from orb.domain.base.ports import LoggingPort, ConfigurationPort
+ return AzureTemplateAdapter(
+ logger=c.get(LoggingPort),
+ config=c.get(ConfigurationPort),
+ )
+
+container.register_singleton(TemplateAdapterPort, create_azure_template_adapter)
+```
+
+**AWS reference:** `register_aws_services_with_di` in `src/orb/providers/aws/registration.py`.
+
+---
+
+### TemplateExampleGeneratorPort
+
+**Location:** `src/orb/domain/base/ports/template_example_generator_port.py`
+
+**What it does:** Generates example template JSON for the `orb template generate` command. ORB resolves this port from the DI container and calls it to produce provider-appropriate example output. No live API connection is required; the generator uses handler class metadata only.
+
+**When to register:** In `register__services_with_di(container)`, alongside the template adapter.
+
+**How to register:**
+
+```python
+from orb.domain.base.ports.template_example_generator_port import TemplateExampleGeneratorPort
+
+def create_azure_example_generator(c):
+ from orb.providers.azure.adapters.template_example_generator_adapter import (
+ AzureTemplateExampleGeneratorAdapter,
+ )
+ return AzureTemplateExampleGeneratorAdapter()
+
+container.register_singleton(TemplateExampleGeneratorPort, create_azure_example_generator)
+```
+
+**AWS reference:** The `TemplateExampleGeneratorPort` block inside `register_aws_services_with_di`.
+
+---
+
+## OperationOutcome contract
+
+Every strategy method that performs a cloud operation returns `OperationOutcome`, a discriminated union defined in `src/orb/domain/base/operation_outcome.py`:
+
+```python
+OperationOutcome = Accepted | Completed | RequiresFollowUp | Failed
+```
+
+Choose the correct variant based on what the cloud API actually tells you:
+
+| Variant | Use when |
+|---|---|
+| `Accepted` | The provider acknowledged the request but resources are not yet in their final state. Include a provider-side tracking ID in `request_id` and the in-flight resource IDs in `pending_resource_ids`. The orchestration layer will poll `get_status` until a terminal outcome is returned. |
+| `Completed` | All resources have reached their terminal state in this call. Include the final resource IDs in `resource_ids`. |
+| `RequiresFollowUp` | The provider acknowledged the request but a domain-level follow-up action is needed beyond simple polling (for example, a webhook registration or a secondary API call). Populate a `FollowUpContext` describing what to do next. |
+| `Failed` | The operation failed. Set `recoverable=True` for transient failures (throttles, temporary capacity shortages) and `False` for hard failures (invalid configuration, permission denied). |
+
+**AWS example — `acquire` always returns `Accepted`:**
+
+```python
+async def acquire(self, request: Request) -> OperationOutcome:
+ result = await self.execute_operation(operation)
+ if not result.success:
+ return Failed(error=result.error_message or "acquire failed", recoverable=False)
+ return Accepted(
+ request_id=str(request.request_id),
+ pending_resource_ids=result.data.get("resource_ids", []),
+ )
+```
+
+EC2 Fleet, SpotFleet, and RunInstances all accept the request immediately and let instances transition through `pending → running` asynchronously. The correct outcome is always `Accepted`.
+
+**Azure ARM example — `return_machines` with multi-step async teardown:**
+
+Azure resource deletion may trigger a long-running ARM operation that requires a separate status poll URL:
+
+```python
+async def return_machines(
+ self, machine_ids: list[str], request: Request
+) -> OperationOutcome:
+ response = await self._arm_client.begin_delete(resource_ids=machine_ids)
+ if response.needs_follow_up:
+ return RequiresFollowUp(
+ context=AzureArmFollowUpContext(
+ operation_url=response.poll_url,
+ resource_ids=machine_ids,
+ follow_up_kind="arm_async_delete",
+ )
+ )
+ if response.done:
+ return Completed(resource_ids=machine_ids)
+ return Accepted(
+ request_id=response.operation_id,
+ pending_resource_ids=machine_ids,
+ )
+```
+
+Always dispatch on `OperationOutcome` exhaustively using `match` + `assert_never` in calling code so pyright catches any future variant additions at compile time.
+
+---
+
+## Anti-patterns
+
+The following patterns must not appear in new provider code. Each one creates a coupling that prevents new providers from being added without editing shared infrastructure.
+
+### Do not edit `cli/args.py` for provider-specific flags
+
+`cli/args.py` iterates `CLISpecRegistry.all()`. Adding flags for a specific provider here leaks provider knowledge into shared code and means all users see flags that may not apply to their provider.
+
+```python
+# Wrong — in src/orb/cli/args.py
+parser.add_argument("--azure-subscription-id", ...)
+
+# Right — in src/orb/providers/azure/cli/azure_cli_spec.py
+class AzureCLISpec:
+ def add_arguments(self, parser) -> None:
+ parser.add_argument("--azure-subscription-id", ...)
+```
+
+### Do not add `if provider_type == "x"` branches in shared services
+
+Branching on provider type in shared services (template defaults service, provisioning orchestration service, scheduler) means every new provider requires editing code it should not know about.
+
+```python
+# Wrong — in any shared service
+if provider_type == "azure":
+ apply_azure_defaults(template)
+elif provider_type == "aws":
+ apply_aws_defaults(template)
+
+# Right — register a DefaultsLoader and let the registry dispatch
+loader = DefaultsLoaderRegistry.get(provider_type)
+if loader:
+ defaults = loader.load_defaults()
+```
+
+### Do not use `getattr(template, f"validate_{provider_type}")` dynamic dispatch
+
+String-keyed `getattr` dispatch is invisible to the type checker. Renames silently break at runtime and there is no way to enumerate valid provider types statically.
+
+```python
+# Wrong
+if hasattr(template, f"validate_{provider_type}"):
+ getattr(template, f"validate_{provider_type}")()
+
+# Right — use TemplateExtensionRegistry for unconditional dispatch
+extension_class = TemplateExtensionRegistry.get_extension_class(provider_type)
+if extension_class:
+ extension_class.model_validate(template.provider_config or {})
+```
+
+### Do not add provider-specific fields to the shared `TemplateAggregate`
+
+The domain template aggregate is provider-agnostic. Adding an `azure_resource_group` field to `domain/template/template_aggregate.py` forces all providers to handle a field they do not own and breaks the provider isolation guarantee.
+
+```python
+# Wrong — in src/orb/domain/template/template_aggregate.py
+azure_resource_group: str | None = None
+
+# Right — in AzureTemplateExtensionConfig (a Pydantic model inside the Azure package)
+class AzureTemplateExtensionConfig(BaseModel):
+ resource_group: str = ""
+```
+
+### Do not add provider-specific fields to the shared `TemplateDTO`
+
+`TemplateDTO` is the serialisation boundary between the application and API layers. Provider-specific fields belong in the `provider_config: BaseModel | None` extension field populated by `TemplateExtensionRegistry`, not as top-level DTO attributes.
+
+```python
+# Wrong — in src/orb/application/dto/template_dto.py
+azure_vm_size: str | None = None
+
+# Right — TemplateDTO.provider_config carries AzureTemplateExtensionConfig
+# automatically when the extension is registered
+```
+
+### Do not add provider strings to domain value objects
+
+`domain/base/value_objects.py` and similar domain files must not contain string literals for specific providers. Use `ProviderType` where a typed enum is appropriate, and registries for everything else.
+
+```python
+# Wrong — in src/orb/domain/base/value_objects.py
+KNOWN_PROVIDERS = ["aws", "azure", "gcp"]
+
+# Right — ProviderRegistry.registered_providers() returns this list dynamically
+```
+
+### Do not add provider-specific imports to shared infrastructure
+
+Shared infrastructure files (`infrastructure/scheduler/`, `api/server.py`, etc.) must not import from `providers//`. Doing so creates a hard dependency that prevents the package from being imported in environments where that provider's SDK is not installed.
+
+```python
+# Wrong — in src/orb/infrastructure/scheduler/hostfactory/hostfactory_strategy.py
+from orb.providers.aws.utilities.ec2.instances import derive_cpu_ram_from_instance_type
+
+# Right — FieldMappingRegistry.get(provider_type).resolve_cpu_ram(vm_size)
+```
+
+---
+
+## Test layout
+
+Mirror the source layout under `tests/providers//`:
+
+```
+tests/providers//
+ conftest.py # shared fixtures for this provider
+ unit/ # pure unit tests, no cloud calls, no mocks of cloud SDK
+ test__strategy.py
+ test__cli_spec.py
+ test__template_extension.py
+ test__field_mapping.py
+ moto/ # mocked integration tests (use a mock SDK equivalent)
+ conftest.py
+ test__acquire.py
+ test__return.py
+ live/ # real-cloud tests, gated by --live flag
+ conftest.py # skips all tests unless --live is passed
+ test__connectivity.py
+ test__roundtrip.py
+ contract/ # contract tests verifying OperationOutcome variants
+ test_outcome_variants.py
+```
+
+Use the per-provider `conftest.py` to define fixtures that supply mock clients, provider configs, and sample request/template domain objects. Keep `moto/` and `live/` sub-packages separate so CI can include mocked tests and exclude live tests without test selection gymnastics.
+
+Gate live tests with a custom pytest marker:
+
+```python
+# tests/providers//live/conftest.py
+import pytest
+
+def pytest_collection_modifyitems(config, items):
+ if not config.getoption("--live", default=False):
+ skip = pytest.mark.skip(reason="pass --live to run real-cloud tests")
+ for item in items:
+ if "live" in str(item.fspath):
+ item.add_marker(skip)
+```
+
+The AWS provider tests are the reference layout:
+
+- Unit tests: `tests/providers/aws/unit/`
+- Mocked integration tests: `tests/providers/aws/moto/`
+- Real-AWS tests: `tests/providers/aws/live/`
+- Contract tests: `tests/providers/aws/contract/`
+
+---
+
+## Cross-references
+
+- [Clean Architecture](../architecture/clean_architecture.md) — layer boundaries enforced by architecture tests
+- [Strategy Pattern](../patterns/strategy_pattern.md) — how `BaseProviderStrategy` and `ProviderRegistry` work together
+- [Ports and Adapters](../patterns/ports_and_adapters.md) — the port/registry decoupling pattern used throughout
+- AWS reference implementation: `src/orb/providers/aws/registration.py`
diff --git a/makefiles/common.mk b/makefiles/common.mk
index 9a1f2e8cb..87cd6709d 100644
--- a/makefiles/common.mk
+++ b/makefiles/common.mk
@@ -42,7 +42,7 @@ TESTS := tests
TESTS_UNIT := $(TESTS)/unit
TESTS_INTEGRATION := $(TESTS)/integration
TESTS_E2E := $(TESTS)/e2e
-TESTS_ONMOTO := $(TESTS)/onmoto
+TESTS_ONMOTO := $(TESTS)/providers/aws/moto
TESTS_PERFORMANCE := $(TESTS)/performance
TESTS_INFRASTRUCTURE := $(TESTS)/infrastructure
TESTS_PROVIDERS := $(TESTS)/providers
diff --git a/makefiles/deploy.mk b/makefiles/deploy.mk
index 3146447ce..2f8910899 100644
--- a/makefiles/deploy.mk
+++ b/makefiles/deploy.mk
@@ -20,14 +20,6 @@ container-show-version: dev-install ## Show container version info
container-run: dev-install ## Run container build
@./dev-tools/container/container_dispatcher.py run
-container-health-check: ## Poll container health check endpoint
- @./dev-tools/container/container_health_check.py
-
-test-docker: ## Run Docker test suite (build, startup, security, pytest tests/docker/)
- @./dev-tools/testing/test-docker.sh
-
-# Dummy targets removed (consolidated in quality.mk)
-
# @SECTION Documentation
# Documentation targets
docs: dev-install ## Build/manage docs (usage: make docs [serve] [deploy] [version=X.X.X] [list] [delete=X.X.X] [clean])
diff --git a/makefiles/dev.mk b/makefiles/dev.mk
index 114078eab..83e09198f 100644
--- a/makefiles/dev.mk
+++ b/makefiles/dev.mk
@@ -53,8 +53,8 @@ test: dev-install ## Run tests (usage: make test [unit|integration|e2e|coverage
test-report: dev-install ## Generate comprehensive test report (PRESERVE: used by ci.yml)
./dev-tools/testing/run_tests.py --all --coverage --junit-xml=test-results-combined.xml --cov-xml=coverage-combined.xml --html-coverage --maxfail=1 --timeout=60
-system-tests: dev-install ## Run system integration tests
- @uv run python -m pytest tests/onaws/test_onaws.py -v -m manual_aws --no-cov --tb=long
+system-tests: dev-install ## Run system integration tests (real AWS)
+ @uv run python -m pytest tests/providers/aws/live/test_onaws.py -v -m manual_aws --no-cov --tb=long
# Backward compatibility aliases (direct calls to avoid loops)
test-unit: dev-install
@@ -93,6 +93,36 @@ test-html: dev-install
test-docker: dev-install ## Run Docker containerization tests
@./dev-tools/testing/test-docker.sh
+# Provider-aware test targets
+# Parallel worker count for pytest. Capped at half the available CPU cores so
+# parallel test runs (especially live-AWS, which spawn ORB server subprocesses,
+# boto3 sessions, and long polls per worker) don't exhaust system memory or
+# saturate the network. Override per-invocation: make test-providers-aws-live PYTEST_WORKERS=4
+# Use $(shell ...) so the math runs once when make starts, not on every recipe.
+_HALF_CPU := $(shell python3 -c 'import os; print(max(1, (os.cpu_count() or 2) // 2))')
+PYTEST_WORKERS ?= $(_HALF_CPU)
+
+test-no-live: dev-install ## Run all tests except live cloud suites (pre-PR check)
+ @uv run pytest --no-cov -q -ra -n $(PYTEST_WORKERS) --ignore=tests/providers/aws/live
+
+test-providers: dev-install ## Run all provider tests except live
+ @uv run pytest --no-cov -q -ra -n $(PYTEST_WORKERS) tests/providers --ignore=tests/providers/aws/live
+
+test-providers-aws: dev-install ## Run AWS provider unit + moto tests
+ @uv run pytest --no-cov -q -ra -n $(PYTEST_WORKERS) tests/providers/aws/unit tests/providers/aws/moto
+
+test-providers-aws-unit: dev-install ## Run AWS provider unit tests only
+ @uv run pytest --no-cov -q -ra -n $(PYTEST_WORKERS) tests/providers/aws/unit
+
+test-providers-aws-moto: dev-install ## Run AWS moto (mocked) tests only
+ @uv run pytest --no-cov -q -ra -n $(PYTEST_WORKERS) tests/providers/aws/moto
+
+test-providers-aws-live: dev-install ## Run AWS live tests (requires real AWS credentials; parallel by default)
+ @uv run --extra api pytest --no-cov -q -ra -n $(PYTEST_WORKERS) --live tests/providers/aws/live
+
+test-architecture: dev-install ## Run architecture compliance tests
+ @uv run pytest --no-cov -q -ra -n $(PYTEST_WORKERS) tests/unit/architecture tests/unit/test_architectural_compliance.py
+
# Dummy targets removed (consolidated in quality.mk)
# @SECTION Development Tools
diff --git a/pyproject.toml b/pyproject.toml
index 32fed287c..3892049ae 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,9 +42,7 @@ classifiers = [
requires-python = ">=3.10"
dependencies = [
- # Core dependencies (always needed)
- "boto3>=1.42.21",
- "botocore>=1.42.21",
+ # Core dependencies (always needed, never provider-specific)
"pydantic>=2.0.0",
"pydantic-settings>=2.0.0",
"sqlalchemy>=2.0.0",
@@ -55,9 +53,43 @@ dependencies = [
"requests>=2.31.0",
"PyYAML>=6.0.0", # Exported API (lazy-loaded)
"jsonschema>=4.17.0",
+ # AWS provider deps — currently in core for backward compatibility.
+ # The canonical install path going forward is `pip install orb-py[aws]`,
+ # which already works today. When the second provider lands these will
+ # move to the [aws] extra and a major-version bump will signal the
+ # breaking change for operators who installed without extras.
+ "boto3>=1.42.21",
+ "botocore>=1.42.21",
]
[project.optional-dependencies]
+# ── Provider extras ────────────────────────────────────────────────────────
+# AWS provider — explicit opt-in alias for the AWS deps currently in core.
+# Pinning the same versions keeps `pip install orb-py[aws]` resolving identically
+# to the bare install and future-proofs operator install commands for the day
+# AWS deps move out of core.
+aws = [
+ "boto3>=1.42.21",
+ "botocore>=1.42.21",
+]
+
+# Placeholder entries for future providers (add when provider is implemented)
+# azure = [
+# "azure-mgmt-compute>=30.0.0",
+# "azure-identity>=1.15.0",
+# "azure-mgmt-network>=25.0.0",
+# ]
+# gcp = [
+# "google-cloud-compute>=1.14.0",
+# "google-auth>=2.23.0",
+# ]
+
+# Meta-extra: all currently implemented providers
+all-providers = [
+ "orb-py[aws]",
+]
+
+# ── Feature extras ─────────────────────────────────────────────────────────
# CLI enhancements (colored output)
cli = [
"rich>=13.3.0",
@@ -72,20 +104,34 @@ api = [
"jinja2>=3.1.0",
]
-# Monitoring & observability (for API/production deployments)
+# Monitoring & observability (provider-neutral — no boto3 instrumentation here)
monitoring = [
"opentelemetry-api>=1.20.0",
"opentelemetry-sdk>=1.20.0",
"opentelemetry-instrumentation-fastapi>=0.41b0",
- "opentelemetry-instrumentation-boto>=0.41b0",
"opentelemetry-instrumentation-sqlalchemy>=0.41b0",
"prometheus-client>=0.17.0",
"psutil>=5.9.0",
]
-# All optional features
+# AWS-specific monitoring (boto3 instrumentation; requires [aws])
+monitoring-aws = [
+ "orb-py[aws,monitoring]",
+ "opentelemetry-instrumentation-boto>=0.41b0",
+]
+
+# All optional features (providers + features)
all = [
- "orb-py[cli,api,monitoring]",
+ "orb-py[cli,api,monitoring,all-providers]",
+]
+
+# ── Test extras ────────────────────────────────────────────────────────────
+# AWS test dependencies — moto + response mocking
+test-aws = [
+ "orb-py[aws]",
+ "moto[ec2,iam,ec2instanceconnect,autoscaling,ssm,dynamodb,sts,sqs,sns]>=5.1.19,<6.0.0",
+ "responses>=0.24.0,<1.0.0",
+ "requests-mock>=1.11.0,<2.0.0",
]
# ---------------------------------------------------------------------------
@@ -151,7 +197,7 @@ ci = [
"wheel>=0.41.3,<1.0.0",
]
dev = [
- "orb-py[ci]",
+ "orb-py[ci,all-providers]",
"virtualenv>=20.26.6",
"pre-commit>=3.5.0,<5.0.0",
"bump2version>=1.0.1,<2.0.0",
@@ -227,6 +273,8 @@ ci = [
"peewee>=3.18.3",
# Security override for safety's vulnerable marshmallow dependency (CVE-2025-68480)
"marshmallow>=4.1.2",
+ # Security override for lxml (CVE-2026-41066) — transitive via cyclonedx-bom + pip-audit
+ "lxml>=6.1.0",
# Documentation
"mkdocs>=1.5.0,<2.0.0",
@@ -291,7 +339,7 @@ include-package-data = true
[tool.pytest.ini_options]
testpaths = ["tests"]
-norecursedirs = ["tests/onaws"]
+norecursedirs = ["tests/providers/aws/live"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
@@ -323,6 +371,7 @@ markers = [
"simulator_limitation: Assertion weakened due to known simulator gap",
"requires_real_provider: Skipped unless --run-real-provider flag passed",
"moto: Tests using moto-mocked AWS (no real credentials required)",
+ "live: Tests requiring real AWS credentials (enabled with --live or --run-aws)",
"sdk: Tests exercising the ORBClient SDK interface",
"cli: marks CLI integration tests",
"benchmark: marks benchmark/performance tests",
@@ -373,7 +422,7 @@ title = "Open Resource Broker Coverage Report"
output = "coverage.xml"
[tool.ruff]
-target-version = "py39"
+target-version = "py310"
line-length = 100
exclude = [
".eggs",
diff --git a/src/orb/api/routers/templates.py b/src/orb/api/routers/templates.py
index 1e92c0c0d..8580209eb 100644
--- a/src/orb/api/routers/templates.py
+++ b/src/orb/api/routers/templates.py
@@ -56,7 +56,7 @@ class TemplateCreateRequest(APIRequest):
template_id: str
name: Optional[str] = None
description: Optional[str] = None
- provider_api: Optional[str] = "aws"
+ provider_api: Optional[str] = None
image_id: Optional[str] = None
instance_type: Optional[str] = None
key_name: Optional[str] = None
@@ -223,7 +223,7 @@ async def create_template(
template_id=template_dict["template_id"],
name=template_dict.get("name"),
description=template_dict.get("description"),
- provider_api=template_dict.get("provider_api") or "aws",
+ provider_api=template_dict.get("provider_api"),
instance_type=template_dict.get("instance_type"),
image_id=template_dict.get("image_id") or "",
tags=template_dict.get("tags") or {},
diff --git a/src/orb/api/server.py b/src/orb/api/server.py
index 7e5581f65..6a73859cc 100644
--- a/src/orb/api/server.py
+++ b/src/orb/api/server.py
@@ -230,83 +230,28 @@ def _create_auth_strategy(auth_config: Any) -> Any:
"""
Create authentication strategy based on configuration.
+ Delegates config extraction entirely to each strategy's ``from_auth_config``
+ classmethod via the auth registry. No per-strategy dispatch lives here.
+
Args:
- auth_config: Authentication configuration
+ auth_config: AuthConfig instance
Returns:
- Authentication strategy instance or None
+ Authentication strategy instance, or None if the strategy name is unknown
+
+ Raises:
+ ConfigurationError: If the strategy is known but its config is invalid
"""
logger = get_logger(__name__)
strategy_name = getattr(auth_config, "strategy", "unknown")
try:
auth_registry = get_auth_registry()
+ return auth_registry.get_strategy(strategy_name, auth_config)
- if strategy_name == "none":
- return auth_registry.get_strategy("none", enabled=False)
-
- elif strategy_name == "bearer_token":
- bearer_config = auth_config.bearer_token or {}
- secret_key = bearer_config.get("secret_key")
- if not secret_key:
- raise ConfigurationError(
- "Bearer token authentication requires a secret_key in auth.bearer_token config. "
- "Set HF_AUTH_BEARER_SECRET_KEY or configure auth.bearer_token.secret_key."
- )
- if len(secret_key.encode()) < 32:
- raise ConfigurationError(
- "Bearer token secret_key must be at least 32 bytes for security."
- )
- return auth_registry.get_strategy(
- "bearer_token",
- secret_key=secret_key,
- algorithm=bearer_config.get("algorithm", "HS256"),
- token_expiry=bearer_config.get("token_expiry", 3600),
- enabled=True,
- )
-
- elif strategy_name == "iam":
- iam_config = (auth_config.provider_auth or {}).get("iam") or {}
- # Register AWS IAM strategy if not already registered
- try:
- from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
-
- auth_registry.register_strategy("iam", IAMAuthStrategy)
- except ImportError:
- logger.warning("AWS IAM strategy not available", exc_info=True)
- return None
-
- return auth_registry.get_strategy(
- "iam",
- region=iam_config.get("region", "us-east-1"),
- profile=iam_config.get("profile"),
- required_actions=iam_config.get("required_actions", []),
- enabled=True,
- )
-
- elif strategy_name == "cognito":
- cognito_config = (auth_config.provider_auth or {}).get("cognito") or {}
- # Register AWS Cognito strategy if not already registered
- try:
- from orb.providers.aws.auth.cognito_strategy import CognitoAuthStrategy
-
- auth_registry.register_strategy("cognito", CognitoAuthStrategy)
- except ImportError:
- logger.warning("AWS Cognito strategy not available", exc_info=True)
- return None
-
- return auth_registry.get_strategy(
- "cognito",
- user_pool_id=cognito_config.get("user_pool_id", ""),
- client_id=cognito_config.get("client_id", ""),
- region=cognito_config.get("region", "us-east-1"),
- enabled=True,
- )
-
- else:
- logger.error("Unknown authentication strategy: %s", strategy_name)
- return None
-
+ except ValueError:
+ logger.error("Unknown authentication strategy: %s", strategy_name)
+ return None
except Exception as e:
raise ConfigurationError(f"Failed to create auth strategy '{strategy_name}': {e}") from e
diff --git a/src/orb/application/commands/request_creation_handlers.py b/src/orb/application/commands/request_creation_handlers.py
index 71b0607c7..572381038 100644
--- a/src/orb/application/commands/request_creation_handlers.py
+++ b/src/orb/application/commands/request_creation_handlers.py
@@ -11,6 +11,7 @@
CreateReturnRequestCommand,
)
from orb.application.ports.query_bus_port import QueryBusPort
+from orb.application.services.provider_validation_service import ProviderValidationService
from orb.application.services.provisioning_orchestration_service import (
ProvisioningOrchestrationService,
)
@@ -24,7 +25,9 @@
LoggingPort,
ProviderSelectionPort,
)
+from orb.domain.request.aggregate import Request
from orb.domain.request.request_identifiers import RequestId
+from orb.domain.request.request_types import RequestStatus
from orb.domain.request.value_objects import RequestType
@@ -46,6 +49,7 @@ def __init__(
query_bus: QueryBusPort, # QueryBus is required for template lookup
provider_selection_port: ProviderSelectionPort,
provisioning_service: ProvisioningOrchestrationService,
+ provider_validation_service: ProviderValidationService,
) -> None:
"""Initialize the instance."""
super().__init__(logger, event_publisher, error_handler)
@@ -55,7 +59,6 @@ def __init__(
self._provider_selection_port = provider_selection_port
# Initialize services
- from orb.application.services.provider_validation_service import ProviderValidationService
from orb.application.services.request_creation_service import RequestCreationService
from orb.application.services.request_status_management_service import (
RequestStatusManagementService,
@@ -64,9 +67,7 @@ def __init__(
self._request_creation_service = RequestCreationService(logger)
self._provisioning_service = provisioning_service
self._status_service = RequestStatusManagementService(uow_factory, logger)
- self._provider_validation_service = ProviderValidationService(
- container, logger, provider_selection_port
- )
+ self._provider_validation_service = provider_validation_service
async def validate_command(self, command: CreateRequestCommand) -> None:
"""Validate create request command."""
@@ -136,8 +137,6 @@ async def _load_template(self, template_id: str) -> Any:
def _handle_dry_run(self, request: Any) -> Any:
"""Handle dry-run request."""
- from orb.domain.request.value_objects import RequestStatus
-
self.logger.info(
"Skipping actual provisioning for request %s (dry-run mode)",
request.request_id,
@@ -256,8 +255,6 @@ async def execute_command(self, command: CreateReturnRequestCommand) -> None:
self.logger.info("Creating return request for machines: %s", command.machine_ids)
try:
- from orb.domain.request.aggregate import Request
-
domain_config = self._container.get(DomainConfigurationService)
prefix = domain_config.get_return_request_prefix()
force_return = command.force_return or False
@@ -424,7 +421,6 @@ async def _update_request_to_in_progress(self, request: Any) -> None:
try:
from orb.application.dto.commands import UpdateRequestStatusCommand
from orb.application.ports.command_bus_port import CommandBusPort
- from orb.domain.request.request_types import RequestStatus
update_command = UpdateRequestStatusCommand(
request_id=str(request.request_id),
@@ -468,7 +464,6 @@ async def _execute_deprovisioning_for_request(
if skipped_ids:
from orb.application.dto.commands import UpdateRequestStatusCommand
from orb.application.ports.command_bus_port import CommandBusPort
- from orb.domain.request.request_types import RequestStatus
skipped_str = ", ".join(skipped_ids)
update_command = UpdateRequestStatusCommand(
@@ -489,8 +484,8 @@ async def _execute_deprovisioning_for_request(
skipped_str,
)
else:
- await self._update_request_to_completed(request)
- self.logger.info("Termination initiated for request %s", request.request_id)
+ await self._update_request_to_terminating(request)
+ self.logger.info("Termination accepted for request %s", request.request_id)
else:
await self._update_request_to_failed(request, provisioning_result.get("errors", []))
@@ -517,75 +512,88 @@ def _update_machines_to_pending(self, machine_ids: list[str]) -> None:
)
uow.machines.save(updated_machine)
- async def _update_request_to_failed(self, request: Any, errors: list[str]) -> None:
- """Update request status to failed."""
- try:
- from orb.application.dto.commands import UpdateRequestStatusCommand
- from orb.application.ports.command_bus_port import CommandBusPort
- from orb.domain.request.request_types import RequestStatus
-
- error_message = "; ".join(errors) if errors else "Deprovisioning failed"
+ async def _update_request_status(
+ self, request: Request, status: RequestStatus, message: str
+ ) -> None:
+ """Update request status via the command bus with a UoW fallback for FAILED.
- update_command = UpdateRequestStatusCommand(
- request_id=str(request.request_id),
- status=RequestStatus.FAILED,
- message=f"Return request failed: {error_message}",
- )
+ On command-bus failure the error is logged. For FAILED status a direct
+ UoW write is attempted so the request always reaches a terminal state and
+ callers do not poll forever. For all other statuses the caller decides
+ how to handle the failure (e.g. _update_request_to_terminating re-raises
+ so the deprovisioning loop can apply its own fallback).
+ """
+ from orb.application.dto.commands import UpdateRequestStatusCommand
+ from orb.application.ports.command_bus_port import CommandBusPort
+ update_command = UpdateRequestStatusCommand(
+ request_id=str(request.request_id),
+ status=status,
+ message=message,
+ )
+ try:
command_bus = self._container.get(CommandBusPort)
await command_bus.execute(update_command)
-
- self.logger.info("Updated request %s status to failed", request.request_id)
-
+ self.logger.info("Updated request %s status to %s", request.request_id, status)
except Exception as update_error:
self.logger.error(
- "Failed to update request status: %s",
+ "Failed to update request %s status to %s: %s",
+ request.request_id,
+ status,
update_error,
exc_info=True,
)
- # Force-write terminal status directly to prevent permanent stuck state
- try:
- from orb.domain.request.request_types import RequestStatus
+ if status == RequestStatus.FAILED:
+ # Force-write terminal status directly to prevent permanent stuck state
+ try:
+ with self.uow_factory.create_unit_of_work() as uow:
+ stuck_request = uow.requests.get_by_id(request.request_id)
+ if stuck_request:
+ stuck_request = stuck_request.update_status(
+ RequestStatus.FAILED,
+ "System error: failed to update status after double failure",
+ force=True,
+ )
+ uow.requests.save(stuck_request)
+ except Exception as final_error:
+ self.logger.critical(
+ "CRITICAL: Failed to mark request %s as failed after double failure. "
+ "Request is stuck in IN_PROGRESS. Manual intervention required. Error: %s",
+ request.request_id,
+ final_error,
+ )
+ else:
+ raise
- with self.uow_factory.create_unit_of_work() as uow:
- stuck_request = uow.requests.get_by_id(request.request_id)
- if stuck_request:
- stuck_request = stuck_request.update_status(
- RequestStatus.FAILED,
- "System error: failed to update status after double failure",
- force=True,
- )
- uow.requests.save(stuck_request)
- except Exception as final_error:
- self.logger.critical(
- "CRITICAL: Failed to mark request %s as failed after double failure. "
- "Request is stuck in IN_PROGRESS. Manual intervention required. Error: %s",
- request.request_id,
- final_error,
- )
- # Nothing more we can do
+ async def _update_request_to_failed(self, request: Any, errors: list[str]) -> None:
+ """Update request status to failed."""
+ error_message = "; ".join(errors) if errors else "Deprovisioning failed"
+ await self._update_request_status(
+ request, RequestStatus.FAILED, f"Return request failed: {error_message}"
+ )
+
+ async def _update_request_to_terminating(self, request: Any) -> None:
+ """Set return request to IN_PROGRESS after termination is accepted by provider.
+
+ Termination is asynchronous — the provider accepted the request but instances
+ are still ``shutting-down``. Using IN_PROGRESS here lets the background sync
+ poll AWS and transition to COMPLETED only when all instances reach ``terminated``.
+ """
+ await self._update_request_status(
+ request,
+ RequestStatus.IN_PROGRESS,
+ "Termination accepted: waiting for instances to reach terminated state",
+ )
async def _update_request_to_completed(self, request: Any) -> None:
"""Update return request status to completed and persist."""
try:
- from orb.application.dto.commands import UpdateRequestStatusCommand
- from orb.application.ports.command_bus_port import CommandBusPort
- from orb.domain.request.request_types import RequestStatus
-
- update_command = UpdateRequestStatusCommand(
- request_id=str(request.request_id),
- status=RequestStatus.COMPLETED,
- message="Return request completed: termination initiated",
+ await self._update_request_status(
+ request,
+ RequestStatus.COMPLETED,
+ "Return request completed: termination initiated",
)
- command_bus = self._container.get(CommandBusPort)
- await command_bus.execute(update_command)
- self.logger.info("Updated request %s status to completed", request.request_id)
except Exception as update_error:
- self.logger.error(
- "Failed to update request status to completed: %s",
- update_error,
- exc_info=True,
- )
# Ensure the request reaches a terminal status so callers don't poll forever
await self._update_request_to_failed(
request, [f"Failed to mark completed: {update_error}"]
diff --git a/src/orb/application/commands/request_sync_handlers.py b/src/orb/application/commands/request_sync_handlers.py
index a8499d815..b255e7f40 100644
--- a/src/orb/application/commands/request_sync_handlers.py
+++ b/src/orb/application/commands/request_sync_handlers.py
@@ -170,7 +170,7 @@ async def execute_command(self, command: SyncRequestCommand) -> None:
if new_status:
await status_service.update_request_status(
- request, new_status, status_message or ""
+ request, new_status, status_message or "", provider_metadata
)
self.logger.info("Successfully synced request: %s", command.request_id)
diff --git a/src/orb/application/factories/request_dto_factory.py b/src/orb/application/factories/request_dto_factory.py
index 64b6829b9..a23cee194 100644
--- a/src/orb/application/factories/request_dto_factory.py
+++ b/src/orb/application/factories/request_dto_factory.py
@@ -1,7 +1,10 @@
"""Request DTO factory for data transformation."""
+from typing import Optional
+
from orb.application.machine.result_mapping import map_machine_status_to_result
from orb.application.request.dto import MachineReferenceDTO, RequestDTO
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
from orb.domain.machine.aggregate import Machine
from orb.domain.request.aggregate import Request
from orb.domain.request.request_types import RequestType
@@ -11,9 +14,21 @@ class RequestDTOFactory:
"""Factory for creating RequestDTOs from domain objects."""
def create_from_domain(
- self, request: Request, machines: list[Machine] | None = None
+ self,
+ request: Request,
+ machines: list[Machine] | None = None,
+ fulfilment: Optional[ProviderFulfilment] = None,
) -> RequestDTO:
- """Create RequestDTO from domain objects."""
+ """Create RequestDTO from domain objects.
+
+ Args:
+ request: Request domain aggregate.
+ machines: Optional list of Machine aggregates to embed as references.
+ fulfilment: Optional ProviderFulfilment to surface capacity fields
+ (target_units, fulfilled_units, running_count, pending_count).
+ When None, ``RequestDTO.from_domain`` falls back to
+ ``request.metadata["last_fulfilment"]`` if present.
+ """
if machines is None:
machines = []
@@ -26,7 +41,7 @@ def create_from_domain(
machine.status.value, request.request_type
),
status=machine.status.value,
- private_ip_address=machine.private_ip or "",
+ private_ip_address=machine.private_ip or None,
public_ip_address=machine.public_ip,
instance_type=str(machine.instance_type) if machine.instance_type else None,
price_type=machine.price_type,
@@ -41,7 +56,9 @@ def create_from_domain(
]
# Create RequestDTO using existing factory method
- return RequestDTO.from_domain(request, machine_references=machine_references)
+ return RequestDTO.from_domain(
+ request, machine_references=machine_references, fulfilment=fulfilment
+ )
def map_machine_status_to_result(self, status: str, request_type: RequestType) -> str:
"""Map machine status to result code."""
diff --git a/src/orb/application/machine/dto.py b/src/orb/application/machine/dto.py
index 7b4c11bc4..94cc8cd3d 100644
--- a/src/orb/application/machine/dto.py
+++ b/src/orb/application/machine/dto.py
@@ -42,7 +42,7 @@ class MachineDTO(BaseDTO):
security_group_ids: Optional[list[str]] = Field(default_factory=list)
status_reason: Optional[str] = None
termination_time: Optional[Union[int, str]] = None
- tags: Optional[Any] = None
+ tags: Optional[dict[str, str]] = None
provider_data: dict[str, Any] = Field(default_factory=dict)
version: int = 0
@@ -108,7 +108,7 @@ def from_domain(cls, machine: Machine, timestamp_format: str = "auto") -> "Machi
if machine.termination_time is not None
else None
),
- tags=machine.tags,
+ tags=machine.tags.to_dict() if machine.tags else None,
)
def to_dict(self) -> dict[str, Any]:
diff --git a/src/orb/application/queries/request_query_handlers.py b/src/orb/application/queries/request_query_handlers.py
index defc7947d..077f2ded4 100644
--- a/src/orb/application/queries/request_query_handlers.py
+++ b/src/orb/application/queries/request_query_handlers.py
@@ -12,12 +12,14 @@
ListReturnRequestsQuery,
)
from orb.application.dto.responses import RequestDTO
+from orb.application.factories.request_dto_factory import RequestDTOFactory
from orb.application.request.queries import ListRequestsQuery
from orb.application.services.machine_sync_service import MachineSyncService
from orb.application.services.provider_registry_service import ProviderRegistryService
+from orb.application.services.request_query_service import RequestQueryService
from orb.application.services.request_status_service import RequestStatusService
from orb.domain.base import UnitOfWorkFactory
-from orb.domain.base.exceptions import EntityNotFoundError
+from orb.domain.base.exceptions import EntityNotFoundError, ProviderContractError
from orb.domain.base.ports import ContainerPort, ErrorHandlingPort, LoggingPort
from orb.domain.services.generic_filter_service import GenericFilterService
@@ -44,9 +46,6 @@ def __init__(
self._cache_service = self._get_cache_service()
self.event_publisher = self._get_event_publisher()
- from orb.application.factories.request_dto_factory import RequestDTOFactory
- from orb.application.services.request_query_service import RequestQueryService
-
self._query_service = RequestQueryService(uow_factory, logger)
self._status_service = RequestStatusService(uow_factory, logger)
self._dto_factory = RequestDTOFactory()
@@ -80,6 +79,13 @@ async def execute_query(self, query: GetRequestQuery) -> RequestDTO:
# no domain invariants are enforced here, no domain events are raised.
# The alternative (background polling) requires infrastructure that doesn't
# exist yet. If you want to remove this, implement a background sync first.
+ #
+ # Terminal requests are immutable — skip the provider describe round-trip
+ # and return the persisted machines directly.
+ db_machines: list = []
+ if request.status.is_terminal():
+ db_machines = await self._query_service.get_machines_for_request(request)
+ return self._dto_factory.create_from_domain(request, db_machines)
try:
await self._machine_sync_service.populate_missing_machine_ids(request)
db_machines = await self._query_service.get_machines_for_request(request)
@@ -95,16 +101,18 @@ async def execute_query(self, query: GetRequestQuery) -> RequestDTO:
)
if new_status:
await self._status_service.update_request_status(
- request, new_status, status_message or ""
+ request, new_status, status_message or "", provider_metadata
)
request = await self._query_service.get_request(query.request_id)
+ except ProviderContractError:
+ raise
except Exception as sync_err:
self.logger.warning(
"Error syncing request %s, returning stored state: %s",
query.request_id,
sync_err,
)
- return self._dto_factory.create_from_domain(request, [])
+ return self._dto_factory.create_from_domain(request, db_machines)
# Deliberate query-time mutation: record when this request was polled for status.
# first_status_check is set once; last_status_check is updated on every poll.
@@ -116,7 +124,17 @@ async def execute_query(self, query: GetRequestQuery) -> RequestDTO:
machine_objects = await self._query_service.get_machines_for_request(request)
request_dto = self._dto_factory.create_from_domain(request, machine_objects)
- if self._cache_service and self._cache_service.is_caching_enabled():
+ # Only cache terminal requests. Non-terminal requests (in_progress,
+ # pending, provisioning) must be re-synced against the provider on
+ # every poll so that status transitions (→ completed, → failed) are
+ # picked up promptly. Caching in_progress responses causes the
+ # poll loop to receive stale status for the full TTL window and the
+ # request appears stuck until the cache expires.
+ if (
+ self._cache_service
+ and self._cache_service.is_caching_enabled()
+ and request.status.is_terminal()
+ ):
self._cache_service.cache_request(query.request_id, request_dto)
self.logger.info("Retrieved request: %s", query.request_id)
@@ -206,15 +224,13 @@ async def execute_query(self, query: ListRequestsQuery) -> list[RequestDTO]:
end_idx = start_idx + (query.limit or 50)
requests = requests[start_idx:end_idx]
+ dto_factory = RequestDTOFactory()
request_dtos = []
for request in requests:
machines = []
if request.machine_ids:
machines = uow.machines.find_by_ids(request.machine_ids)
- from orb.application.factories.request_dto_factory import RequestDTOFactory
-
- dto_factory = RequestDTOFactory()
request_dto = dto_factory.create_from_domain(request, machines)
request_dtos.append(request_dto)
@@ -243,16 +259,65 @@ def __init__(
logger: LoggingPort,
error_handler: ErrorHandlingPort,
generic_filter_service: GenericFilterService,
+ machine_sync_service: MachineSyncService,
) -> None:
super().__init__(logger, error_handler)
self.uow_factory = uow_factory
self._generic_filter_service = generic_filter_service
+ self._machine_sync_service = machine_sync_service
+ self._status_service = RequestStatusService(uow_factory, logger)
+ self._query_service = RequestQueryService(uow_factory, logger)
+ self._dto_factory = RequestDTOFactory()
async def execute_query(self, query: ListReturnRequestsQuery) -> list[RequestDTO]:
"""Execute list return requests query."""
self.logger.info("Listing return requests")
try:
+ with self.uow_factory.create_unit_of_work() as uow:
+ from orb.domain.request.value_objects import RequestType
+
+ return_requests = uow.requests.find_by_type(RequestType.RETURN)
+
+ # Read-through sync: refresh each non-terminal return request from
+ # live provider state so a return that has actually completed at the
+ # provider transitions to COMPLETED in the DB. Without this, a
+ # caller polling list_return_requests would see a request stuck in
+ # IN_PROGRESS forever and may retry the return — triggering
+ # provider-side double-decrement (e.g. ASG capacity off by one).
+ for request in return_requests:
+ if request.status.is_terminal():
+ continue
+ try:
+ db_machines = await self._query_service.get_machines_for_request(request)
+ (
+ provider_machines,
+ provider_metadata,
+ ) = await self._machine_sync_service.fetch_provider_machines(
+ request, db_machines
+ )
+ (
+ synced_machines,
+ _,
+ ) = await self._machine_sync_service.sync_machines_with_provider(
+ request, db_machines, provider_machines
+ )
+ new_status, status_message = (
+ self._status_service.determine_status_from_machines(
+ db_machines, synced_machines, request, provider_metadata
+ )
+ )
+ if new_status:
+ await self._status_service.update_request_status(
+ request, new_status, status_message or "", provider_metadata
+ )
+ except Exception as sync_err:
+ self.logger.warning(
+ "Sync failed for return request %s, returning stored state: %s",
+ request.request_id.value,
+ sync_err,
+ )
+
with self.uow_factory.create_unit_of_work() as uow:
from orb.domain.request.value_objects import RequestType
@@ -264,10 +329,7 @@ async def execute_query(self, query: ListReturnRequestsQuery) -> list[RequestDTO
if request.machine_ids:
machines = uow.machines.find_by_ids(request.machine_ids)
- from orb.application.factories.request_dto_factory import RequestDTOFactory
-
- dto_factory = RequestDTOFactory()
- request_dto = dto_factory.create_from_domain(request, machines)
+ request_dto = self._dto_factory.create_from_domain(request, machines)
request_dtos.append(request_dto)
if query.machine_names:
@@ -330,10 +392,8 @@ def __init__(
self._generic_filter_service = generic_filter_service
self._machine_sync_service = machine_sync_service
self._status_service = RequestStatusService(uow_factory, logger)
-
- from orb.application.services.request_query_service import RequestQueryService
-
self._query_service = RequestQueryService(uow_factory, logger)
+ self._dto_factory = RequestDTOFactory()
async def execute_query(self, query: ListActiveRequestsQuery) -> list[RequestDTO]:
"""Execute list active requests query."""
@@ -383,7 +443,7 @@ async def execute_query(self, query: ListActiveRequestsQuery) -> list[RequestDTO
)
if new_status:
await self._status_service.update_request_status(
- request, new_status, status_message or ""
+ request, new_status, status_message or "", provider_metadata
)
except Exception as sync_err:
self.logger.warning(
@@ -397,10 +457,7 @@ async def execute_query(self, query: ListActiveRequestsQuery) -> list[RequestDTO
request = await self._query_service.get_request(str(request.request_id.value))
db_machines = await self._query_service.get_machines_for_request(request)
- from orb.application.factories.request_dto_factory import RequestDTOFactory
-
- dto_factory = RequestDTOFactory()
- request_dto = dto_factory.create_from_domain(request, db_machines)
+ request_dto = self._dto_factory.create_from_domain(request, db_machines)
request_dtos.append(request_dto)
if query.filter_expressions:
diff --git a/src/orb/application/request/dto.py b/src/orb/application/request/dto.py
index 897561f3d..4c05adce3 100644
--- a/src/orb/application/request/dto.py
+++ b/src/orb/application/request/dto.py
@@ -7,6 +7,7 @@
from orb.application.dto.base import BaseDTO
from orb.application.machine.result_mapping import map_machine_status_to_result
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
from orb.domain.machine.aggregate import Machine
from orb.domain.request.aggregate import Request
from orb.domain.request.request_types import RequestType
@@ -19,7 +20,7 @@ class MachineReferenceDTO(BaseDTO):
name: str = ""
result: str # 'executing', 'fail', or 'succeed'
status: str
- private_ip_address: str = ""
+ private_ip_address: Optional[str] = None
public_ip_address: Optional[str] = None # Already using the expected API field name
instance_type: Optional[str] = None
price_type: Optional[str] = None
@@ -54,7 +55,7 @@ def from_machine(cls, machine: Machine, request_type: RequestType) -> "MachineRe
name=machine.display_name,
result=map_machine_status_to_result(status, request_type=rt_str),
status=status,
- private_ip_address=machine.private_ip or "",
+ private_ip_address=machine.private_ip or None,
public_ip_address=machine.public_ip,
instance_type=str(machine.instance_type) if machine.instance_type else None,
price_type=machine.price_type,
@@ -115,6 +116,17 @@ class RequestDTO(BaseDTO):
provider_data: dict[str, Any] = Field(default_factory=dict)
version: int = 0
resource_ids: list[str] = Field(default_factory=list)
+ # Structured error block surfaced when status is failed or partial.
+ # Populated from error_details["provider_error"] (or legacy "aws_error") when
+ # an AWS API error was captured.
+ error: Optional[dict[str, Any]] = None
+ # Capacity fields from ProviderFulfilment — present when the provider emits them.
+ # For weighted fleets target_units and fulfilled_units reflect capacity units;
+ # for RunInstances they reflect instance count (1 unit = 1 instance).
+ target_units: Optional[int] = None
+ fulfilled_units: Optional[int] = None
+ running_count: Optional[int] = None
+ pending_count: Optional[int] = None
@classmethod
def from_domain(
@@ -122,6 +134,7 @@ def from_domain(
request: Request,
verbose: bool = False,
machine_references: Optional[list["MachineReferenceDTO"]] = None,
+ fulfilment: Optional[ProviderFulfilment] = None,
) -> "RequestDTO":
"""
Create DTO from domain object.
@@ -146,6 +159,26 @@ def from_domain(
MachineReferenceDTO.from_machine(m, request.request_type) for m in domain_refs
]
+ # Resolve the ProviderFulfilment to use for capacity fields.
+ # Priority: explicit arg > cached snapshot in metadata["last_fulfilment"].
+ resolved_fulfilment: Optional[ProviderFulfilment] = fulfilment
+ if resolved_fulfilment is None and request.metadata:
+ cached = request.metadata.get("last_fulfilment")
+ if isinstance(cached, dict):
+ try:
+ resolved_fulfilment = ProviderFulfilment(**cached)
+ except (TypeError, KeyError):
+ resolved_fulfilment = None
+
+ # Build structured error block from error_details when available.
+ # Accept both "provider_error" (new) and legacy "aws_error" so that
+ # records persisted before this deploy continue to surface the error block.
+ error_block: Optional[dict[str, Any]] = None
+ if request.error_details:
+ error_block = request.error_details.get("provider_error") or request.error_details.get(
+ "aws_error"
+ )
+
# Create the DTO with all available fields
return cls(
request_id=str(request.request_id),
@@ -177,6 +210,13 @@ def from_domain(
provider_data=request.provider_data,
version=request.version,
resource_ids=request.resource_ids,
+ error=error_block,
+ # Capacity fields — populated from resolved ProviderFulfilment when available.
+ # Falls back to metadata["last_fulfilment"] when no explicit arg is supplied.
+ target_units=resolved_fulfilment.target_units if resolved_fulfilment else None,
+ fulfilled_units=resolved_fulfilment.fulfilled_units if resolved_fulfilment else None,
+ running_count=resolved_fulfilment.running_count if resolved_fulfilment else None,
+ pending_count=resolved_fulfilment.pending_count if resolved_fulfilment else None,
)
def to_dict(self, verbose: bool = False) -> dict[str, Any]:
@@ -203,6 +243,17 @@ def to_dict(self, verbose: bool = False) -> dict[str, Any]:
# Remove machine_references field as it's replaced by machines
result.pop("machine_references", None)
+ # Include error block only when present (failed / partial requests).
+ if self.error:
+ result["error"] = self.error
+ else:
+ result.pop("error", None)
+
+ # Include capacity fields only when populated (fleet/weighted-capacity requests).
+ for cap_field in ("target_units", "fulfilled_units", "running_count", "pending_count"):
+ if result.get(cap_field) is None:
+ result.pop(cap_field, None)
+
# Remove fields based on detail level
if not include_details:
result.pop("metadata", None)
diff --git a/src/orb/application/services/machine_sync_service.py b/src/orb/application/services/machine_sync_service.py
index 8f58705cd..477da7ec6 100644
--- a/src/orb/application/services/machine_sync_service.py
+++ b/src/orb/application/services/machine_sync_service.py
@@ -80,6 +80,7 @@ async def fetch_provider_machines(
"resource_ids": request.resource_ids,
"provider_api": request.provider_api,
"template_id": request.template_id,
+ "requested_count": request.requested_count,
}
# Fallback to instance-level discovery for requests without resource tracking
elif db_machines:
@@ -261,6 +262,7 @@ async def sync_machines_with_provider(
or existing.vpc_id != provider_machine.vpc_id
or existing.status_reason != provider_machine.status_reason
or existing.provider_data != provider_machine.provider_data
+ or (provider_machine.resource_id and not existing.resource_id)
)
# Debug logging
@@ -288,6 +290,8 @@ async def sync_machines_with_provider(
machine_data["vpc_id"] = provider_machine.vpc_id
machine_data["version"] = existing.version + 1
machine_data["tags"] = provider_machine.tags
+ if provider_machine.resource_id and not existing.resource_id:
+ machine_data["resource_id"] = provider_machine.resource_id
updated_machine = Machine.model_validate(machine_data)
to_upsert.append(updated_machine)
diff --git a/src/orb/application/services/orchestration/dtos.py b/src/orb/application/services/orchestration/dtos.py
index 9a9a650ed..06a69365d 100644
--- a/src/orb/application/services/orchestration/dtos.py
+++ b/src/orb/application/services/orchestration/dtos.py
@@ -73,6 +73,7 @@ class ReturnMachinesOutput:
status: str
message: str = ""
skipped_machines: list[str] = dataclasses.field(default_factory=list)
+ machine_ids: list[str] = dataclasses.field(default_factory=list)
@dataclasses.dataclass(frozen=True)
@@ -155,8 +156,8 @@ class GetTemplateOutput:
@dataclasses.dataclass(frozen=True)
class CreateTemplateInput:
template_id: str
- provider_api: str
image_id: str
+ provider_api: Optional[str] = None
name: Optional[str] = None
description: Optional[str] = None
instance_type: Optional[str] = None
diff --git a/src/orb/application/services/orchestration/return_machines.py b/src/orb/application/services/orchestration/return_machines.py
index 6ee743774..19dbc8033 100644
--- a/src/orb/application/services/orchestration/return_machines.py
+++ b/src/orb/application/services/orchestration/return_machines.py
@@ -108,6 +108,7 @@ async def execute(self, input: ReturnMachinesInput) -> ReturnMachinesOutput: #
return ReturnMachinesOutput(
request_id=primary_request_id,
status=status,
+ machine_ids=machine_ids,
)
async def _poll_until_terminal(self, request_id: str, timeout_seconds: int) -> str:
diff --git a/src/orb/application/services/provider_validation_service.py b/src/orb/application/services/provider_validation_service.py
index 2decb9225..bb9245a12 100644
--- a/src/orb/application/services/provider_validation_service.py
+++ b/src/orb/application/services/provider_validation_service.py
@@ -10,7 +10,6 @@
from orb.domain.template.template_aggregate import Template
from orb.domain.base.ports import ContainerPort, LoggingPort, ProviderSelectionPort
-from orb.domain.base.ports.configuration_port import ConfigurationPort
from orb.domain.base.ports.provider_validation_port import ProviderValidationPort
from orb.domain.base.results import ProviderSelectionResult
@@ -30,25 +29,6 @@ def __init__(
self._provider_selection_port = provider_selection_port
self._validator = validator
- async def validate_provider_availability(self) -> None:
- """Validate that providers are available."""
- config_manager = self._container.get(ConfigurationPort)
- provider_config = config_manager.get_provider_config()
-
- if provider_config:
- for provider_instance in provider_config.get_active_providers():
- self._provider_selection_port.register_provider_strategy(
- provider_instance.type, provider_instance
- )
-
- available_strategies = self._provider_selection_port.get_available_strategies()
- if not available_strategies:
- error_msg = "No provider strategies available - cannot create machine requests"
- self.logger.error(error_msg)
- raise ValueError(error_msg)
-
- self.logger.debug("Available provider strategies: %s", available_strategies)
-
async def select_and_validate_provider(self, template: Template) -> ProviderSelectionResult:
"""Select provider and validate template compatibility."""
selection_result = self._provider_selection_port.select_provider_for_template(template)
diff --git a/src/orb/application/services/provisioning_orchestration_service.py b/src/orb/application/services/provisioning_orchestration_service.py
index 4136f1f67..388fc378a 100644
--- a/src/orb/application/services/provisioning_orchestration_service.py
+++ b/src/orb/application/services/provisioning_orchestration_service.py
@@ -1,14 +1,22 @@
"""Service for orchestrating provider provisioning operations."""
-from dataclasses import dataclass
+import asyncio
+from dataclasses import dataclass, field
from datetime import datetime, timezone
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any, Callable, assert_never
if TYPE_CHECKING:
from orb.domain.base.ports.provider_selection_port import ProviderSelectionPort
from orb.infrastructure.resilience.strategy.circuit_breaker import CircuitBreakerStrategy
from orb.domain.base.exceptions import QuotaError
+from orb.domain.base.operation_outcome import (
+ Accepted,
+ Completed,
+ Failed,
+ OperationOutcome,
+ RequiresFollowUp,
+)
from orb.domain.base.ports import ConfigurationPort, ContainerPort, LoggingPort, ProviderConfigPort
from orb.domain.base.results import ProviderSelectionResult
from orb.domain.request.aggregate import Request
@@ -19,7 +27,24 @@
@dataclass
class ProvisioningResult:
- """Result of provisioning operation."""
+ """Result of provisioning operation.
+
+ The ``outcome`` field carries the typed :class:`OperationOutcome` returned
+ by the provider. ``is_final`` is preserved for backward compatibility and
+ is derived from ``outcome`` when it is present.
+
+ ``Accepted`` → ``is_final = False`` (provider is still processing)
+ ``Completed`` → ``is_final = True``
+ ``RequiresFollowUp`` → ``is_final = False`` (background work remains)
+ ``Failed`` → ``is_final = True``
+ ``None`` (legacy) → honour the explicit ``is_final`` value
+
+ Provider error fields (all optional, only set on failure):
+ ``provider_error_code`` — provider API error code (e.g. ``UnauthorizedOperation``)
+ ``provider_error_message`` — human-readable message from the provider response
+ ``provider_request_id`` — provider request ID for support cases
+ ``error_source`` — service.operation label (e.g. ``aws.ec2.RunInstances``)
+ """
success: bool
resource_ids: list[str]
@@ -29,6 +54,47 @@ class ProvisioningResult:
error_message: str | None = None
fulfilled_count: int = 0
is_final: bool = True
+ outcome: OperationOutcome | None = field(default=None)
+ # Provider error detail fields — populated when the failure originates from
+ # a provider API call so callers can surface actionable diagnostics to the user.
+ provider_error_code: str | None = None
+ provider_error_message: str | None = None
+ provider_request_id: str | None = None
+ error_source: str | None = None
+
+ def __post_init__(self) -> None:
+ """Derive ``is_final`` from ``outcome`` when an outcome is provided."""
+ if self.outcome is not None:
+ match self.outcome:
+ case Accepted():
+ self.is_final = False
+ case Completed():
+ self.is_final = True
+ case RequiresFollowUp():
+ self.is_final = False
+ case Failed():
+ self.is_final = True
+ case _ as unreachable:
+ assert_never(unreachable)
+
+
+def _extract_aws_error_fields(exc: BaseException) -> dict[str, Any]:
+ """Extract provider error fields from a provider exception (if applicable).
+
+ Returns a dict suitable for **-unpacking into ProvisioningResult. When the
+ exception carries no provider error attributes the dict will contain only
+ None values so the ProvisioningResult fields stay empty (safe default).
+ """
+ provider_error_code: str | None = getattr(exc, "aws_error_code", None)
+ provider_error_message: str | None = getattr(exc, "aws_error_message", None)
+ provider_request_id: str | None = getattr(exc, "aws_request_id", None)
+ error_source: str | None = getattr(exc, "error_source", None)
+ return {
+ "provider_error_code": provider_error_code,
+ "provider_error_message": provider_error_message,
+ "provider_request_id": provider_request_id,
+ "error_source": error_source,
+ }
class ProvisioningOrchestrationService:
@@ -58,12 +124,14 @@ async def execute_provisioning(
default_config: dict[str, Any] = {
"max_retries": request_config.get("fulfillment_max_retries", 3),
"timeout_seconds": request_config.get("fulfillment_timeout_seconds", 300),
+ "dispatch_timeout_seconds": request_config.get("dispatch_timeout_seconds", 300),
"batch_size": request_config.get("fulfillment_batch_size", 1000),
"fallback_template_id": request_config.get("fulfillment_fallback_template_id"),
}
config = {**default_config, **request.metadata.get("fulfillment_config", {})}
max_retries: int = int(config["max_retries"])
timeout_seconds: float = float(config["timeout_seconds"])
+ dispatch_timeout_seconds: float = float(config["dispatch_timeout_seconds"])
batch_size: int = int(config["batch_size"])
started_at = datetime.now(timezone.utc)
@@ -104,7 +172,7 @@ async def execute_provisioning(
try:
last_result = await self._dispatch_single_attempt(
- template, request, selection_result, attempt_count
+ template, request, selection_result, attempt_count, dispatch_timeout_seconds
)
except Exception as e:
if not isinstance(e, CircuitBreakerOpenError):
@@ -168,7 +236,7 @@ async def execute_provisioning(
request.requested_count,
remaining,
)
- request, persist_ok = self._persist_acquiring(request)
+ request, persist_ok = await asyncio.to_thread(self._persist_acquiring, request)
if not persist_ok:
self._logger.warning(
"ACQUIRING persist failed for request %s on attempt %d — "
@@ -249,6 +317,7 @@ async def _dispatch_single_attempt(
request: Request,
selection_result: ProviderSelectionResult,
count: int,
+ dispatch_timeout_seconds: float = 300.0,
) -> ProvisioningResult:
"""Dispatch a single provisioning attempt for `count` instances."""
try:
@@ -277,9 +346,10 @@ async def _dispatch_single_attempt(
self._provider_config_port.get_provider_instance_config(selection_result.provider_name)
- result = await self._provider_selection_port.execute_operation(
- selection_result.provider_name, operation
- )
+ async with asyncio.timeout(dispatch_timeout_seconds):
+ result = await self._provider_selection_port.execute_operation(
+ selection_result.provider_name, operation
+ )
if result.success:
self._logger.debug("Provider result.data: %s", result.data)
@@ -300,6 +370,25 @@ async def _dispatch_single_attempt(
if result.routing_info:
merged_provider_data.update(result.routing_info)
+ # Build a typed OperationOutcome from the ProviderResult payload.
+ # AWS provider returns request-IDs with pending instances (async
+ # model) — express that as Accepted rather than pretending it is
+ # immediately Completed.
+ is_immediately_final = (
+ not has_capacity_error and len(instances) >= count
+ ) or fulfillment_final
+ if is_immediately_final:
+ outcome: OperationOutcome = Completed(
+ resource_ids=resource_ids,
+ metadata=merged_provider_data,
+ )
+ else:
+ outcome = Accepted(
+ request_id=str(request.request_id),
+ pending_resource_ids=resource_ids,
+ metadata=merged_provider_data,
+ )
+
self._record_provider_success(selection_result.provider_name)
return ProvisioningResult(
success=True,
@@ -308,8 +397,8 @@ async def _dispatch_single_attempt(
instances=instances,
provider_data=merged_provider_data,
fulfilled_count=len(instances),
- is_final=(not has_capacity_error and len(instances) >= count)
- or fulfillment_final,
+ outcome=outcome,
+ # is_final derived from outcome in __post_init__
)
else:
return ProvisioningResult(
@@ -319,12 +408,35 @@ async def _dispatch_single_attempt(
instances=[],
provider_data=result.metadata or {},
error_message=result.error_message,
+ outcome=Failed(
+ error=result.error_message or "Provider returned failure",
+ recoverable=False,
+ ),
)
except CircuitBreakerOpenError:
raise # do not swallow — let it propagate to execute_provisioning
+ except TimeoutError:
+ timeout_msg = f"Dispatch timed out after {dispatch_timeout_seconds:.0f}s"
+ self._logger.warning(
+ "Dispatch timed out after %.1fs for provider %s (request %s)",
+ dispatch_timeout_seconds,
+ selection_result.provider_name,
+ str(request.request_id) if hasattr(request, "request_id") else "unknown",
+ )
+ return ProvisioningResult(
+ success=False,
+ resource_ids=[],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ error_message=timeout_msg,
+ outcome=Failed(error=timeout_msg, recoverable=True),
+ )
+
except QuotaError as e:
+ quota_msg = f"Quota exceeded: {e}"
self._logger.error(
"Quota error during provisioning for template %s: %s",
template.template_id if hasattr(template, "template_id") else "unknown",
@@ -337,17 +449,20 @@ async def _dispatch_single_attempt(
"error_type": type(e).__name__,
},
)
+ aws_fields = _extract_aws_error_fields(e)
return ProvisioningResult(
success=False,
resource_ids=[],
machine_ids=[],
instances=[],
provider_data={},
- error_message=f"Quota exceeded: {e}",
- is_final=True,
+ error_message=quota_msg,
+ outcome=Failed(error=quota_msg, recoverable=False),
+ **aws_fields,
)
except Exception as e:
+ generic_msg = f"Provisioning failed: {e}"
self._logger.error(
"Provisioning dispatch failed for template %s: %s",
template.template_id if hasattr(template, "template_id") else "unknown",
@@ -361,11 +476,14 @@ async def _dispatch_single_attempt(
"error_type": type(e).__name__,
},
)
+ aws_fields = _extract_aws_error_fields(e)
return ProvisioningResult(
success=False,
resource_ids=[],
machine_ids=[],
instances=[],
provider_data={},
- error_message=f"Provisioning failed: {e}",
+ error_message=generic_msg,
+ outcome=Failed(error=generic_msg, recoverable=False),
+ **aws_fields,
)
diff --git a/src/orb/application/services/request_follow_up_context.py b/src/orb/application/services/request_follow_up_context.py
new file mode 100644
index 000000000..f634e07f0
--- /dev/null
+++ b/src/orb/application/services/request_follow_up_context.py
@@ -0,0 +1,17 @@
+"""Re-export shim — FollowUpContext types live in the domain layer.
+
+Import from ``orb.domain.base.follow_up_context`` directly for new code.
+This module exists only for backward compatibility.
+"""
+
+from orb.domain.base.follow_up_context import ( # noqa: F401
+ DeploymentPollingFollowUpContext,
+ FollowUpContext,
+ TerminationFollowUpContext,
+)
+
+__all__ = [
+ "DeploymentPollingFollowUpContext",
+ "FollowUpContext",
+ "TerminationFollowUpContext",
+]
diff --git a/src/orb/application/services/request_status_management_service.py b/src/orb/application/services/request_status_management_service.py
index b45041add..b15e0ca5a 100644
--- a/src/orb/application/services/request_status_management_service.py
+++ b/src/orb/application/services/request_status_management_service.py
@@ -81,7 +81,7 @@ async def update_request_from_provisioning(
)
def _handle_provisioning_failure(self, request: Any, provisioning_result: Any) -> Any:
- """Handle provisioning failure."""
+ """Handle provisioning failure, capturing provider error details when available."""
from orb.domain.request.value_objects import RequestStatus
error_message = (
@@ -95,6 +95,41 @@ def _handle_provisioning_failure(self, request: Any, provisioning_result: Any) -
{"error_message": error_message, "error_type": "ProvisioningFailure"}
)
+ # Persist structured provider error details so they are available to the status
+ # response. Only non-None fields are included to keep error_details clean.
+ provider_error_code: str | None = getattr(provisioning_result, "provider_error_code", None)
+ provider_error_message: str | None = getattr(
+ provisioning_result, "provider_error_message", None
+ )
+ provider_request_id: str | None = getattr(provisioning_result, "provider_request_id", None)
+ error_source: str | None = getattr(provisioning_result, "error_source", None)
+
+ if any([provider_error_code, provider_error_message, provider_request_id, error_source]):
+ aws_error_block: dict[str, Any] = {}
+ if provider_error_code:
+ aws_error_block["code"] = provider_error_code
+ if provider_error_message:
+ aws_error_block["message"] = provider_error_message
+ if error_source:
+ aws_error_block["source"] = error_source
+ if provider_request_id:
+ aws_error_block["aws_request_id"] = provider_request_id
+
+ # Merge into error_details so it survives serialization / persistence.
+ current = dict(request.error_details) if request.error_details else {}
+ current["provider_error"] = aws_error_block
+ # Pydantic freeze-safe: use model_copy for the error_details field.
+ from orb.domain.request.aggregate import Request as RequestAggregate
+
+ if isinstance(request, RequestAggregate):
+ fields = request.model_dump()
+ fields["error_details"] = current
+ fields["version"] = request.version + 1
+ request = RequestAggregate.model_validate(fields)
+ else:
+ # Fallback for mock objects in tests
+ request.error_details = current # type: ignore[attr-defined]
+
return request
def _update_request_status(
diff --git a/src/orb/application/services/request_status_service.py b/src/orb/application/services/request_status_service.py
index cbdbe9e02..35299f910 100644
--- a/src/orb/application/services/request_status_service.py
+++ b/src/orb/application/services/request_status_service.py
@@ -1,9 +1,29 @@
-"""Request status service for business logic."""
-
+"""Request status service for business logic.
+
+Acquire path (fulfilment-based)
+---------------------------------
+The application layer trusts the provider's ``ProviderFulfilment`` verdict
+exclusively. No count math. No provider-specific key inspection.
+
+Every provider's ``check_hosts_status`` MUST return a ``CheckHostsStatusResult``
+with a ``ProviderFulfilment``. If the fulfilment is missing the service raises
+``ProviderContractError`` — a hard error, not a silent fallback.
+
+Return path
+-----------
+``determine_status_from_machines`` still uses the existing machine-status
+counting for return requests because termination is observable via instance
+states (shutting-down → terminated) without a fleet-level capacity concept.
+The return path is unchanged.
+"""
+
+import dataclasses
from typing import Optional, Tuple
from orb.domain.base import UnitOfWorkFactory
+from orb.domain.base.exceptions import ProviderContractError
from orb.domain.base.ports.logging_port import LoggingPort
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
from orb.domain.machine.aggregate import Machine
from orb.domain.request.aggregate import Request
from orb.domain.request.request_types import RequestStatus, RequestType
@@ -27,137 +47,158 @@ def determine_status_from_machines(
request: Request,
provider_metadata: dict,
) -> Tuple[Optional[str], Optional[str]]:
- """Determine request status from machine states."""
- try:
- db_machine_count = len(db_machines)
+ """Determine request status from machine states.
- # Determine new status based on request type
- if request.request_type.value == "return":
- # For return requests: empty provider_machines means instances are gone from AWS → COMPLETED
- if not provider_machines:
- return (
- RequestStatus.COMPLETED.value,
- f"Return request completed: all machines terminated "
- f"(no longer visible in provider) (total in DB: {db_machine_count})",
- )
+ For acquire requests the provider MUST supply a ``ProviderFulfilment``
+ via ``provider_metadata["provider_fulfilment"]``. Any legacy
+ ``fleet_capacity_fulfilment`` key is ignored — the provider contract
+ is the only truth.
- shutting_down_count = sum(
- 1 for m in provider_machines if m.status.value in ["shutting-down", "stopping"]
- )
- terminated_count = sum(
- 1 for m in provider_machines if m.status.value in ["terminated", "stopped"]
+ For return requests the existing machine-state counting logic is used.
+ """
+ try:
+ if request.request_type.value == "return":
+ return self._determine_return_status(
+ db_machines, provider_machines, request, provider_metadata
)
- running_count = sum(1 for m in provider_machines if m.status.value == "running")
- failed_count = sum(1 for m in provider_machines if m.status.value == "failed")
- total_count = len(provider_machines)
-
- # shutting-down/stopping are transient — only terminated/stopped are truly done
- effectively_done_count = terminated_count
- if effectively_done_count == total_count and running_count == 0:
- return (
- RequestStatus.COMPLETED.value,
- f"Return request completed: {terminated_count} terminated, "
- f"{shutting_down_count} shutting down "
- f"(total in DB: {db_machine_count})",
- )
- elif running_count > 0:
- return (
- RequestStatus.IN_PROGRESS.value,
- f"Return in progress: {running_count} machines still running, "
- f"awaiting termination (total in DB: {db_machine_count})",
- )
- elif failed_count > 0:
- return (
- RequestStatus.FAILED.value,
- f"Return request failed: {failed_count} machines failed to terminate "
- f"(total in DB: {db_machine_count})",
- )
- else:
- return RequestStatus.IN_PROGRESS.value, "Instances terminating"
- # Acquisition request logic
else:
- machines_to_check = provider_machines if provider_machines else db_machines
-
- if not machines_to_check:
- return (
- RequestStatus.IN_PROGRESS.value,
- "Status determination failed — will retry",
- )
-
- running_count = sum(1 for m in machines_to_check if m.status.value == "running")
- pending_count = sum(
- 1 for m in machines_to_check if m.status.value in ["pending", "starting"]
- )
- failed_count = sum(1 for m in machines_to_check if m.status.value == "failed")
- total_count = len(machines_to_check)
-
- # Use fleet capacity metrics when available (fleets/ASGs report their own
- # target and fulfilled capacity). Fall back to instance counts otherwise.
- fleet_capacity = provider_metadata.get("fleet_capacity_fulfilment") or {}
- effective_target = (
- fleet_capacity.get("target_capacity_units") or request.requested_count
- )
- fulfilled_from_metadata = fleet_capacity.get("fulfilled_capacity_units")
- effective_fulfilled: float = (
- float(fulfilled_from_metadata)
- if fulfilled_from_metadata is not None
- else float(running_count + pending_count)
+ return self._determine_acquire_status(
+ db_machines, provider_machines, request, provider_metadata
)
-
- # Fleet metadata (FulfilledCapacity) can lag or use floating-point values
- # that don't exactly match target. Use running instance count as the
- # authoritative signal when it meets the requested count.
- fulfillment_final = fleet_capacity.get("fulfillment_final", False)
- fleet_errors = provider_metadata.get("fleet_errors") or []
-
- instance_target = request.requested_count
- if (running_count >= instance_target and failed_count == 0) or (
- effective_fulfilled >= effective_target and failed_count == 0
- ):
- return RequestStatus.COMPLETED.value, "All instances running successfully"
- elif fulfillment_final and pending_count == 0:
- error_detail = (
- f": {'; '.join(e.get('error_code', '') for e in fleet_errors if e.get('error_code'))}"
- if fleet_errors
- else ""
- )
- if running_count > 0:
- return (
- RequestStatus.PARTIAL.value,
- f"{running_count}/{instance_target} instances running{error_detail}",
- )
- else:
- return RequestStatus.FAILED.value, f"All instances failed{error_detail}"
- elif failed_count == total_count and total_count > 0:
- return RequestStatus.FAILED.value, "All instances failed"
- elif pending_count > 0:
- return (
- RequestStatus.IN_PROGRESS.value,
- f"{running_count}/{effective_target} instances running, waiting for {pending_count} more",
- )
- elif running_count > 0 and (running_count + failed_count) >= effective_target:
- return (
- RequestStatus.PARTIAL.value,
- f"{running_count}/{effective_target} instances running",
- )
- elif running_count > 0:
- return (
- RequestStatus.IN_PROGRESS.value,
- f"{running_count}/{effective_target} instances running, waiting for more",
- )
- else:
- return RequestStatus.IN_PROGRESS.value, "Instances starting"
-
+ except ProviderContractError:
+ raise
except Exception as e:
self.logger.error(f"Failed to determine status from machines: {e}")
return RequestStatus.IN_PROGRESS.value, "Status determination failed — will retry"
- async def update_request_status(self, request: Request, status: str, message: str) -> Request:
- """Update request status."""
+ # ------------------------------------------------------------------
+ # Acquire path — trusts ProviderFulfilment exclusively
+ # ------------------------------------------------------------------
+
+ def _determine_acquire_status(
+ self,
+ db_machines: list[Machine],
+ provider_machines: list[Machine],
+ request: Request,
+ provider_metadata: dict,
+ ) -> Tuple[Optional[str], Optional[str]]:
+ """Map ProviderFulfilment state to RequestStatus for acquire requests."""
+ fulfilment: Optional[ProviderFulfilment] = provider_metadata.get("provider_fulfilment")
+
+ if fulfilment is None:
+ raise ProviderContractError(
+ f"Provider {getattr(request, 'provider_name', 'unknown')} did not emit "
+ "ProviderFulfilment for acquire request. Every provider's "
+ "check_hosts_status must return CheckHostsStatusResult with fulfilment."
+ )
+
+ state_map: dict[str, str] = {
+ "fulfilled": RequestStatus.COMPLETED.value,
+ "in_progress": RequestStatus.IN_PROGRESS.value,
+ "partial": RequestStatus.PARTIAL.value,
+ "failed": RequestStatus.FAILED.value,
+ }
+ mapped = state_map.get(fulfilment.state)
+ if mapped is None:
+ # Unknown state — treat as in_progress to be safe
+ self.logger.warning(
+ "Unknown fulfilment state '%s', treating as in_progress", fulfilment.state
+ )
+ return RequestStatus.IN_PROGRESS.value, fulfilment.message
+
+ return mapped, fulfilment.message
+
+ # ------------------------------------------------------------------
+ # Return path — machine-state counting (unchanged)
+ # ------------------------------------------------------------------
+
+ def _determine_return_status(
+ self,
+ db_machines: list[Machine],
+ provider_machines: list[Machine],
+ request: Request,
+ provider_metadata: dict,
+ ) -> Tuple[Optional[str], Optional[str]]:
+ """Determine return request status from machine termination states."""
+ db_machine_count = len(db_machines)
+
+ # For return requests: empty provider_machines means instances are gone from AWS.
+ if not provider_machines:
+ return (
+ RequestStatus.COMPLETED.value,
+ f"Return request completed: all machines terminated "
+ f"(no longer visible in provider) (total in DB: {db_machine_count})",
+ )
+
+ shutting_down_count = sum(
+ 1 for m in provider_machines if m.status.value in ["shutting-down", "stopping"]
+ )
+ terminated_count = sum(
+ 1 for m in provider_machines if m.status.value in ["terminated", "stopped"]
+ )
+ running_count = sum(1 for m in provider_machines if m.status.value == "running")
+ failed_count = sum(1 for m in provider_machines if m.status.value == "failed")
+
+ # Compare against the number of machines the caller submitted for return.
+ completion_target = request.requested_count
+
+ effectively_done_count = terminated_count
+ if effectively_done_count >= completion_target and running_count == 0:
+ return (
+ RequestStatus.COMPLETED.value,
+ f"Return request completed: {terminated_count} terminated, "
+ f"{shutting_down_count} shutting down "
+ f"(total in DB: {db_machine_count})",
+ )
+ elif running_count > 0:
+ return (
+ RequestStatus.IN_PROGRESS.value,
+ f"Return in progress: {running_count} machines still running, "
+ f"awaiting termination (total in DB: {db_machine_count})",
+ )
+ elif failed_count > 0:
+ return (
+ RequestStatus.FAILED.value,
+ f"Return request failed: {failed_count} machines failed to terminate "
+ f"(total in DB: {db_machine_count})",
+ )
+ else:
+ return RequestStatus.IN_PROGRESS.value, "Instances terminating"
+
+ async def update_request_status(
+ self,
+ request: Request,
+ status: str,
+ message: str,
+ provider_metadata: Optional[dict] = None,
+ ) -> Request:
+ """Update request status and optionally cache the latest ProviderFulfilment.
+
+ When ``provider_metadata`` is supplied and contains a ``provider_fulfilment``
+ key, the fulfilment is serialised and stored as ``request.metadata["last_fulfilment"]``.
+ That snapshot is later read by ``RequestDTO.from_domain`` so capacity fields
+ (target_units, fulfilled_units, running_count, pending_count) appear in every
+ response without requiring the caller to re-pass the fulfilment explicitly.
+
+ No-op if the request is already in a terminal state. Terminal requests
+ (COMPLETED/FAILED/CANCELLED/TIMEOUT/PARTIAL) are immutable; re-evaluation
+ of a terminal request during a query-time sync must not attempt to
+ downgrade it.
+ """
+ if request.status.is_terminal():
+ return request
try:
status_enum = RequestStatus(status)
updated_request = request.update_status(status_enum, message)
+ # Cache the latest ProviderFulfilment snapshot so DTO callers can surface it.
+ if provider_metadata:
+ fulfilment = provider_metadata.get("provider_fulfilment")
+ if fulfilment is not None:
+ updated_request = updated_request.with_last_fulfilment(
+ dataclasses.asdict(fulfilment)
+ )
+
# Save updated request
with self.uow_factory.create_unit_of_work() as uow:
uow.requests.save(updated_request)
@@ -178,10 +219,9 @@ def map_machine_status_to_result(self, status: str, request_type: RequestType) -
return "executing"
else:
return "fail"
+ elif status == "running":
+ return "succeed"
+ elif status in ["pending", "launching"]:
+ return "executing"
else:
- if status == "running":
- return "succeed"
- elif status in ["pending", "launching"]:
- return "executing"
- else:
- return "fail"
+ return "fail"
diff --git a/src/orb/application/services/template_defaults_service.py b/src/orb/application/services/template_defaults_service.py
index fb43ae2e5..2895457fe 100644
--- a/src/orb/application/services/template_defaults_service.py
+++ b/src/orb/application/services/template_defaults_service.py
@@ -5,10 +5,13 @@
from orb.domain.base.dependency_injection import injectable
from orb.domain.base.ports.configuration_port import ConfigurationPort
from orb.domain.base.ports.logging_port import LoggingPort
+from orb.domain.base.ports.provider_registry_port import ProviderRegistryPort
from orb.domain.base.utils import extract_provider_type
-from orb.domain.template.extensions import TemplateExtensionRegistry
from orb.domain.template.factory import TemplateFactoryPort
from orb.domain.template.ports.template_defaults_port import TemplateDefaultsPort
+from orb.domain.template.ports.template_extension_registry_port import (
+ TemplateExtensionRegistryPort,
+)
from orb.domain.template.template_aggregate import Template
@@ -32,7 +35,8 @@ def __init__(
config_manager: ConfigurationPort,
logger: LoggingPort,
template_factory: Optional[TemplateFactoryPort] = None,
- extension_registry: Optional[TemplateExtensionRegistry] = None,
+ extension_registry: Optional[TemplateExtensionRegistryPort] = None,
+ provider_registry: Optional[ProviderRegistryPort] = None,
) -> None:
"""
Initialize the template defaults service.
@@ -41,12 +45,14 @@ def __init__(
config_manager: Configuration port for accessing defaults
logger: Logger for debugging and monitoring
template_factory: Factory for creating domain templates
- extension_registry: Registry for provider extensions
+ extension_registry: Registry port for provider extension defaults
+ provider_registry: Registry for resolving provider-contributed defaults
"""
self.config_manager = config_manager
self.logger = logger
self.template_factory = template_factory
- self.extension_registry = extension_registry or TemplateExtensionRegistry
+ self.extension_registry = extension_registry
+ self.provider_registry = provider_registry
def resolve_template_defaults(
self,
@@ -100,7 +106,13 @@ def resolve_template_defaults(
)
# 4. Apply template values (highest priority - only for missing fields)
- if template_dict.get("launch_template_id"):
+ # launch_template_id may be at top level (legacy) or inside provider_config (new path).
+ _pc = template_dict.get("provider_config") or {}
+ _has_lt = bool(
+ template_dict.get("launch_template_id")
+ or (_pc.get("launch_template_id") if isinstance(_pc, dict) else None)
+ )
+ if _has_lt:
lt_fields = [
k
for k in (
@@ -284,18 +296,13 @@ def _get_provider_type_defaults(self, provider_type: str) -> dict[str, Any]:
provider_defaults = provider_config.provider_defaults.get(provider_type) # type: ignore[union-attr]
if provider_defaults and hasattr(provider_defaults, "template_defaults"):
result = provider_defaults.template_defaults or {}
- # Fallback: if no provider_api in template_defaults, read from
- # handlers.defaults.default_handler (e.g. the configured default handler name)
- if not result.get("provider_api") and hasattr(provider_defaults, "handlers"):
- default_handler = (
- provider_defaults.handlers.defaults.default_handler
- if provider_defaults.handlers
- and hasattr(provider_defaults.handlers, "defaults")
- else None
- )
- if default_handler:
+ # Fallback: if no provider_api in template_defaults, delegate to the
+ # provider registry which reads it from the provider's registration.
+ if not result.get("provider_api") and self.provider_registry is not None:
+ default_api = self.provider_registry.get_default_api(provider_type)
+ if default_api:
result = dict(result)
- result["provider_api"] = default_handler
+ result["provider_api"] = default_api
return result
return {}
@@ -464,16 +471,14 @@ def _get_extension_defaults(
"""
extension_defaults = {}
+ if self.extension_registry is None:
+ return extension_defaults
+
try:
- # 1. Get provider type extension defaults
- if self.extension_registry.has_extension(provider_type):
- type_extension_defaults = self.extension_registry.get_extension_defaults(
- provider_type
- )
- extension_defaults.update(type_extension_defaults)
- self.logger.debug(
- "Applied %s type extension defaults", len(type_extension_defaults)
- )
+ # 1. Get provider type extension defaults — safe for unknown providers (returns {})
+ type_extension_defaults = self.extension_registry.get_extension_defaults(provider_type)
+ extension_defaults.update(type_extension_defaults)
+ self.logger.debug("Applied %s type extension defaults", len(type_extension_defaults))
# 2. Get provider instance extension overrides
if provider_instance_name:
@@ -514,14 +519,15 @@ def _get_provider_instance_extension_defaults(
and hasattr(provider, "extensions")
and provider.extensions
):
- # Use extension registry to process the extensions
- if self.extension_registry.has_extension(provider_type):
- return self.extension_registry.get_extension_defaults(
+ if self.extension_registry is not None:
+ # Delegate to extension registry — returns {} for unknown providers,
+ # so this is safe to call unconditionally.
+ result = self.extension_registry.get_extension_defaults(
provider_type, provider.extensions
)
- else:
- # Return raw extensions if no registry entry
- return provider.extensions
+ # Fall back to raw extensions dict when registry has no entry.
+ return result if result else provider.extensions
+ return provider.extensions
return {}
@@ -594,18 +600,6 @@ def validate_template_with_extensions(
validation_result["warnings"].append(f"Domain validation failed: {e}")
validation_result["domain_validation"] = "failed"
- # Check for provider-specific validation
- provider_type = (
- self._get_provider_type(provider_instance_name) if provider_instance_name else None
- )
- if provider_type and hasattr(template, f"validate_{provider_type}"):
- try:
- getattr(template, f"validate_{provider_type}")()
- validation_result[f"{provider_type}_validation"] = "passed"
- except Exception as e:
- validation_result["warnings"].append(f"{provider_type} validation failed: {e}")
- validation_result[f"{provider_type}_validation"] = "failed"
-
except Exception as e:
validation_result["errors"].append(f"Template creation with extensions failed: {e}")
validation_result["is_valid"] = False
diff --git a/src/orb/application/template/commands.py b/src/orb/application/template/commands.py
index 3785fb782..de4114fcd 100644
--- a/src/orb/application/template/commands.py
+++ b/src/orb/application/template/commands.py
@@ -16,7 +16,7 @@ class CreateTemplateCommand(BaseCommand):
template_id: str
name: Optional[str] = None
description: Optional[str] = None
- provider_api: str
+ provider_api: Optional[str] = None
instance_type: Optional[str] = None
image_id: str
tags: dict[str, str] = Field(default_factory=dict)
diff --git a/src/orb/bootstrap/infrastructure_services.py b/src/orb/bootstrap/infrastructure_services.py
index 6a2c8ea12..3fbbd48e0 100644
--- a/src/orb/bootstrap/infrastructure_services.py
+++ b/src/orb/bootstrap/infrastructure_services.py
@@ -35,10 +35,14 @@ def create_template_defaults_service(c):
from orb.application.services.template_defaults_service import (
TemplateDefaultsService,
)
+ from orb.infrastructure.registry.template_extension_registry import (
+ TemplateExtensionRegistryAdapter,
+ )
return TemplateDefaultsService(
config_manager=c.get(ConfigurationPort),
logger=c.get(LoggingPort),
+ extension_registry=TemplateExtensionRegistryAdapter(),
)
from orb.domain.template.ports.template_defaults_port import TemplateDefaultsPort
@@ -73,18 +77,30 @@ def create_template_generation_service(c):
# Register TemplateFactory as a singleton so handlers can receive it via DI
def create_template_factory(c: DIContainer):
+ import importlib
+ import importlib.util
+
from orb.domain.template.factory import TemplateFactory
+ from orb.providers.registration import _REGISTERED_PROVIDERS
factory = TemplateFactory(logger=c.get(LoggingPort))
- try:
- from orb.providers.aws.registration import register_aws_template_factory
-
- register_aws_template_factory(factory, c.get(LoggingPort))
- except ImportError as exc:
- c.get(LoggingPort).debug(
- "AWS provider module not available; AWS-specific templates will not be registered: %s",
- exc,
- )
+ logger_port = c.get(LoggingPort)
+ for _name in _REGISTERED_PROVIDERS:
+ _mod_path = f"orb.providers.{_name}.registration"
+ if importlib.util.find_spec(_mod_path) is None:
+ continue
+ try:
+ _mod = importlib.import_module(_mod_path)
+ _reg_fn = getattr(_mod, f"register_{_name}_template_factory", None)
+ if _reg_fn is not None:
+ _reg_fn(factory, logger_port)
+ except ImportError as exc:
+ logger_port.debug(
+ "%s provider module not available; provider-specific templates will not be"
+ " registered: %s",
+ _name,
+ exc,
+ )
return factory
from orb.domain.template.factory import TemplateFactory, TemplateFactoryPort
diff --git a/src/orb/bootstrap/provider_services.py b/src/orb/bootstrap/provider_services.py
index c1820f159..a8280459f 100644
--- a/src/orb/bootstrap/provider_services.py
+++ b/src/orb/bootstrap/provider_services.py
@@ -62,26 +62,41 @@ def create_machine_sync_service(c):
def _register_provider_utility_services(container: DIContainer) -> None:
- """Register provider-specific utility services only (not provider instances)."""
+ """Register provider-specific utility services for all registered providers."""
+ import importlib
+ import importlib.util
+
+ from orb.providers.registration import _REGISTERED_PROVIDERS
+
logger = get_logger(__name__)
- # Register AWS utility services if available
- try:
- import importlib.util
-
- # Check if AWS provider is available
- if importlib.util.find_spec("orb.providers.aws"):
- try:
- from orb.providers.aws.registration import register_aws_services_with_di
-
- register_aws_services_with_di(container)
- logger.debug("AWS utility services registered with DI")
- except Exception as e:
- logger.warning("Failed to register AWS utility services: %s", str(e))
-
- else:
- logger.debug("AWS provider not available, skipping AWS utility service registration")
- except ImportError:
- logger.debug("AWS provider not available, skipping AWS utility service registration")
- except Exception as e:
- logger.warning("Failed to register AWS utility services: %s", str(e))
+ for name in _REGISTERED_PROVIDERS:
+ mod_path = f"orb.providers.{name}.registration"
+ try:
+ spec = importlib.util.find_spec(mod_path)
+ except (ImportError, ValueError) as e:
+ logger.debug("%s find_spec raised %s; skipping", name, type(e).__name__)
+ continue
+ if spec is None:
+ logger.debug("%s provider not available, skipping utility service registration", name)
+ continue
+ try:
+ mod = importlib.import_module(mod_path)
+
+ # Register DI utility services (e.g. AWS template adapter, clients)
+ di_fn = getattr(mod, f"register_{name}_services_with_di", None)
+ if di_fn is not None:
+ di_fn(container)
+ logger.debug("%s utility services registered with DI", name)
+
+ # Register auth strategies so the auth registry can resolve them
+ # without server.py importing provider classes directly.
+ # Pass None for the logging port; registration is best-effort and
+ # the bootstrap logger (ContextLogger) does not implement LoggingPort.
+ auth_fn = getattr(mod, f"register_{name}_auth_strategies", None)
+ if auth_fn is not None:
+ auth_fn(None)
+ logger.debug("%s auth strategies registered", name)
+
+ except Exception as e:
+ logger.warning("Failed to register %s utility services: %s", name, str(e))
diff --git a/src/orb/cli/args.py b/src/orb/cli/args.py
index f21729560..bd09740a8 100644
--- a/src/orb/cli/args.py
+++ b/src/orb/cli/args.py
@@ -14,6 +14,7 @@
from orb.domain.machine.machine_status import MachineStatus
from orb.domain.request.value_objects import RequestStatus
+from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
try:
import rich.console as _rich_console # type: ignore[import-untyped,import-not-found]
@@ -302,8 +303,8 @@ def add_provider_actions(subparsers):
providers_add.add_argument(
"--provider-type", dest="provider_type", required=True, help="Provider type (e.g. aws)"
)
- providers_add.add_argument("--aws-profile", help="AWS profile name")
- providers_add.add_argument("--aws-region", help="AWS region")
+ for _spec in CLISpecRegistry.all().values():
+ _spec.add_arguments(providers_add)
providers_add.add_argument("--name", help="Provider instance name")
providers_add.add_argument("--discover", action="store_true", help="Discover infrastructure")
@@ -314,8 +315,8 @@ def add_provider_actions(subparsers):
providers_update = subparsers.add_parser("update", help="Update provider configuration")
add_global_arguments(providers_update)
providers_update.add_argument("provider_name", help="Provider instance name")
- providers_update.add_argument("--aws-region", help="Update region")
- providers_update.add_argument("--aws-profile", help="Update profile")
+ for _spec in CLISpecRegistry.all().values():
+ _spec.add_arguments(providers_update)
providers_set_default = subparsers.add_parser("set-default", help="Set default provider")
add_global_arguments(providers_set_default)
@@ -424,6 +425,13 @@ def build_parser() -> tuple[argparse.ArgumentParser, dict]:
Returns:
tuple: (parser, resource_parsers_dict)
"""
+ # Ensure CLI specs are registered before iterating the registry. This is
+ # a lightweight bootstrap (no full provider initialisation) so it is safe
+ # to call before any application context exists.
+ from orb.providers.registration import register_all_provider_cli_specs
+
+ register_all_provider_cli_specs()
+
from orb._package import DESCRIPTION, DOCS_URL
parser = argparse.ArgumentParser(
@@ -709,6 +717,10 @@ def build_parser() -> tuple[argparse.ArgumentParser, dict]:
"--fleet-role",
help="Spot Fleet IAM role ARN or name for template_defaults (non-interactive only)",
)
+ # Inject per-provider CLI flags (e.g. --aws-profile, --aws-region) so that
+ # _get_default_config can use spec.extract_config() in non-interactive mode.
+ for _spec in CLISpecRegistry.all().values():
+ _spec.add_arguments(init_parser)
return parser, resource_parsers
diff --git a/src/orb/config/loader.py b/src/orb/config/loader.py
index c35805b8d..39171cf4f 100644
--- a/src/orb/config/loader.py
+++ b/src/orb/config/loader.py
@@ -192,11 +192,28 @@ def _build_raw_config_from_dict(
def _load_strategy_defaults(cls, config_manager=None) -> dict[str, Any]:
merged: dict[str, Any] = {}
try:
- from orb.providers.registry import get_provider_registry
-
- registry = get_provider_registry()
- registry.ensure_provider_type_registered("aws")
- cls._merge_config(merged, registry.collect_defaults())
+ from orb.providers.registry import DefaultsLoaderRegistry
+
+ # Ensure at least the built-in loaders are registered even when
+ # _load_strategy_defaults is called before a full application
+ # bootstrap (e.g. bare config loading in tests or the CLI parser).
+ # This mirrors what the old pkgutil.iter_modules approach did
+ # implicitly — importing each provider's registration module
+ # triggered side-effect registrations.
+ if not DefaultsLoaderRegistry.registered_providers():
+ from orb.providers.registration import register_all_defaults_loaders
+
+ register_all_defaults_loaders()
+
+ for provider_type, loader in DefaultsLoaderRegistry.all().items():
+ try:
+ defaults = loader.load_defaults()
+ if defaults:
+ cls._merge_config(merged, defaults)
+ except Exception as e:
+ get_config_logger().warning(
+ "Failed to load defaults from provider '%s': %s", provider_type, e
+ )
except Exception as e:
get_config_logger().warning("Failed to load provider defaults: %s", e)
try:
diff --git a/src/orb/config/schemas/cleanup_schema.py b/src/orb/config/schemas/cleanup_schema.py
index b01e2be70..fcb108274 100644
--- a/src/orb/config/schemas/cleanup_schema.py
+++ b/src/orb/config/schemas/cleanup_schema.py
@@ -1,8 +1,17 @@
-"""Re-export shim — CleanupConfig has moved to orb.providers.aws.configuration.cleanup_config."""
+"""Re-export shim — CleanupConfig has moved to orb.providers.aws.configuration.cleanup_config.
-from orb.providers.aws.configuration.cleanup_config import (
- CleanupConfig,
- CleanupResourcesConfig,
-)
+Note: This shim imports from the AWS provider package. When the [aws] extra is not
+installed, CleanupConfig and CleanupResourcesConfig are set to None. Phase 2 of the
+provider-extras migration will invert this dependency so the models live in core.
+"""
+
+try:
+ from orb.providers.aws.configuration.cleanup_config import (
+ CleanupConfig,
+ CleanupResourcesConfig,
+ )
+except ImportError:
+ CleanupConfig = None # type: ignore[assignment,misc]
+ CleanupResourcesConfig = None # type: ignore[assignment,misc]
__all__ = ["CleanupConfig", "CleanupResourcesConfig"]
diff --git a/src/orb/config/schemas/server_schema.py b/src/orb/config/schemas/server_schema.py
index 0601e07b9..dad24eea5 100644
--- a/src/orb/config/schemas/server_schema.py
+++ b/src/orb/config/schemas/server_schema.py
@@ -2,30 +2,105 @@
from typing import Any, Optional
-from pydantic import BaseModel, ConfigDict, Field
+from pydantic import BaseModel, ConfigDict, Field, field_validator
+
+_ALLOWED_JWT_ALGORITHMS = frozenset({"HS256", "HS384", "HS512"})
+
+
+class BearerTokenAuthSubConfig(BaseModel):
+ """Typed sub-configuration for the bearer_token auth strategy."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ secret_key: str = Field(..., description="Secret key for JWT signing/verification (>=32 bytes)")
+ algorithm: str = Field("HS256", description="JWT algorithm (HS256, HS384, or HS512)")
+ token_expiry: int = Field(3600, description="Token expiry in seconds")
+
+ @field_validator("algorithm")
+ @classmethod
+ def _validate_algorithm(cls, value: str) -> str:
+ """Restrict to HMAC algorithms; explicitly reject 'none' and unknown values."""
+ if value.lower() == "none":
+ raise ValueError(
+ "Algorithm 'none' is not permitted — it disables signature verification."
+ )
+ if value not in _ALLOWED_JWT_ALGORITHMS:
+ raise ValueError(
+ f"Unsupported JWT algorithm {value!r}. "
+ f"Allowed values: {sorted(_ALLOWED_JWT_ALGORITHMS)}"
+ )
+ return value
+
+
+class IAMAuthSubConfig(BaseModel):
+ """Typed sub-configuration for AWS IAM auth strategy.
+
+ **Security note — assume_permissions:**
+ Setting ``assume_permissions=True`` bypasses real AWS IAM evaluation and grants
+ every action in ``required_actions`` to any authenticated principal. This is a
+ deliberate development/testing escape hatch and MUST NOT be enabled in production.
+ The IAMAuthStrategy enforces this by requiring the environment variable
+ ``ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY=true`` to be set alongside the config flag;
+ without it the flag is ignored and permissions are denied by default.
+ """
+
+ model_config = ConfigDict(extra="forbid")
+
+ region: str = Field("us-east-1", description="AWS region")
+ profile: Optional[str] = Field(None, description="AWS profile")
+ required_actions: list[str] = Field(default_factory=list, description="Required IAM actions")
+ assume_permissions: bool = Field(
+ False,
+ description=(
+ "DEV/TEST ONLY — grant all required_actions without AWS evaluation. "
+ "Has no effect unless ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY=true is also set."
+ ),
+ )
+
+
+class CognitoAuthSubConfig(BaseModel):
+ """Typed sub-configuration for AWS Cognito auth strategy."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ user_pool_id: str = Field("", description="Cognito User Pool ID")
+ client_id: str = Field("", description="Cognito App Client ID")
+ region: str = Field("us-east-1", description="AWS region")
+ jwks_url: Optional[str] = Field(None, description="JWKS URL (auto-generated if omitted)")
+
+
+class ProviderAuthSubConfig(BaseModel):
+ """Typed sub-configuration for provider-specific auth strategies."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ iam: Optional[IAMAuthSubConfig] = Field(None, description="IAM auth sub-configuration")
+ cognito: Optional[CognitoAuthSubConfig] = Field(
+ None, description="Cognito auth sub-configuration"
+ )
class AuthConfig(BaseModel):
"""Authentication configuration."""
- model_config = ConfigDict(extra="allow") # Allow provider-specific auth configs
+ model_config = ConfigDict(extra="forbid")
enabled: bool = Field(False, description="Enable authentication")
strategy: str = Field(
"none",
- description="Authentication strategy (none, bearer_token, iam, cognito, oauth)",
+ description="Authentication strategy (none, bearer_token, bearer_token_enhanced, iam, cognito)",
)
# Bearer token configuration
- bearer_token: Optional[dict[str, Any]] = Field(
+ bearer_token: Optional[BearerTokenAuthSubConfig] = Field(
None, description="Bearer token strategy configuration"
)
- # OAuth configuration
+ # OAuth configuration (kept as untyped dict for forward compatibility)
oauth: Optional[dict[str, Any]] = Field(None, description="OAuth strategy configuration")
# Provider-specific auth configurations
- provider_auth: Optional[dict[str, Any]] = Field(
+ provider_auth: Optional[ProviderAuthSubConfig] = Field(
None, description="Provider-specific auth configuration"
)
diff --git a/src/orb/domain/base/events/provider_events.py b/src/orb/domain/base/events/provider_events.py
index 701527d95..53339bfb5 100644
--- a/src/orb/domain/base/events/provider_events.py
+++ b/src/orb/domain/base/events/provider_events.py
@@ -6,13 +6,13 @@
from pydantic import Field
from orb.domain.base.events.base_events import DomainEvent
-from orb.domain.base.provider_interfaces import ProviderInstanceState, ProviderType
+from orb.domain.base.provider_interfaces import ProviderInstanceState
class ProviderOperationEvent(DomainEvent):
"""Event raised for provider operations."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
operation_type: str # e.g., "create_instance", "terminate_instance"
provider_resource_type: str # e.g., "instance", "volume"
provider_resource_id: Optional[str] = Field(default=None)
@@ -23,7 +23,7 @@ def model_post_init(self, __context: Any) -> None:
"""Initialize aggregate information after model creation."""
# Set the base class fields
object.__setattr__(self, "aggregate_id", self.provider_resource_id or str(uuid4()))
- object.__setattr__(self, "aggregate_type", f"{self.provider_type.value}_resource")
+ object.__setattr__(self, "aggregate_type", f"{self.provider_type}_resource")
super().model_post_init(__context)
if not self.operation_type:
raise ValueError("Operation type cannot be empty")
@@ -34,15 +34,15 @@ def model_post_init(self, __context: Any) -> None:
class ProviderRateLimitEvent(DomainEvent):
"""Event raised when provider rate limiting occurs."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
service_name: str
operation_name: str
retry_after: Optional[int] = Field(default=None) # seconds
def model_post_init(self, __context: Any) -> None:
"""Initialize aggregate information after model creation."""
- object.__setattr__(self, "aggregate_id", f"{self.provider_type.value}_{self.service_name}")
- object.__setattr__(self, "aggregate_type", f"{self.provider_type.value}_service")
+ object.__setattr__(self, "aggregate_id", f"{self.provider_type}_{self.service_name}")
+ object.__setattr__(self, "aggregate_type", f"{self.provider_type}_service")
super().model_post_init(__context)
if not self.service_name:
raise ValueError("Service name cannot be empty")
@@ -53,7 +53,7 @@ def model_post_init(self, __context: Any) -> None:
class ProviderCredentialsEvent(DomainEvent):
"""Event raised for provider credentials operations."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
credential_type: str # e.g., "access_key", "service_account", "managed_identity"
operation: str # e.g., "refresh", "validate", "expire"
status: str # success, failure, warning
@@ -61,8 +61,8 @@ class ProviderCredentialsEvent(DomainEvent):
def model_post_init(self, __context: Any) -> None:
"""Initialize aggregate information after model creation."""
- object.__setattr__(self, "aggregate_id", f"{self.provider_type.value}_credentials")
- object.__setattr__(self, "aggregate_type", f"{self.provider_type.value}_auth")
+ object.__setattr__(self, "aggregate_id", f"{self.provider_type}_credentials")
+ object.__setattr__(self, "aggregate_type", f"{self.provider_type}_auth")
super().model_post_init(__context)
if not self.credential_type:
raise ValueError("Credential type cannot be empty")
@@ -75,7 +75,7 @@ def model_post_init(self, __context: Any) -> None:
class ProviderResourceStateChangedEvent(DomainEvent):
"""Event raised when provider resource state changes."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
resource_type: str
resource_id: str
previous_state: Optional[ProviderInstanceState] = Field(default=None)
@@ -85,9 +85,7 @@ class ProviderResourceStateChangedEvent(DomainEvent):
def model_post_init(self, __context: Any) -> None:
"""Initialize aggregate information after model creation."""
object.__setattr__(self, "aggregate_id", self.resource_id)
- object.__setattr__(
- self, "aggregate_type", f"{self.provider_type.value}_{self.resource_type}"
- )
+ object.__setattr__(self, "aggregate_type", f"{self.provider_type}_{self.resource_type}")
super().model_post_init(__context)
if not self.resource_type:
raise ValueError("Resource type cannot be empty")
@@ -98,7 +96,7 @@ def model_post_init(self, __context: Any) -> None:
class ProviderConfigurationEvent(DomainEvent):
"""Event raised for provider configuration changes."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
configuration_type: str # e.g., "region", "endpoint", "timeout"
old_value: Optional[str] = Field(default=None)
new_value: Optional[str] = Field(default=None)
@@ -106,8 +104,8 @@ class ProviderConfigurationEvent(DomainEvent):
def model_post_init(self, __context: Any) -> None:
"""Initialize aggregate information after model creation."""
- object.__setattr__(self, "aggregate_id", f"{self.provider_type.value}_config")
- object.__setattr__(self, "aggregate_type", f"{self.provider_type.value}_configuration")
+ object.__setattr__(self, "aggregate_id", f"{self.provider_type}_config")
+ object.__setattr__(self, "aggregate_type", f"{self.provider_type}_configuration")
super().model_post_init(__context)
if not self.configuration_type:
raise ValueError("Configuration type cannot be empty")
@@ -116,7 +114,7 @@ def model_post_init(self, __context: Any) -> None:
class ProviderHealthCheckEvent(DomainEvent):
"""Event raised for provider health checks."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
service_name: str
health_status: str # healthy, unhealthy, degraded
response_time_ms: Optional[int] = Field(default=None)
@@ -124,8 +122,8 @@ class ProviderHealthCheckEvent(DomainEvent):
def model_post_init(self, __context: Any) -> None:
"""Initialize aggregate information after model creation."""
- object.__setattr__(self, "aggregate_id", f"{self.provider_type.value}_{self.service_name}")
- object.__setattr__(self, "aggregate_type", f"{self.provider_type.value}_health")
+ object.__setattr__(self, "aggregate_id", f"{self.provider_type}_{self.service_name}")
+ object.__setattr__(self, "aggregate_type", f"{self.provider_type}_health")
super().model_post_init(__context)
if not self.service_name:
raise ValueError("Service name cannot be empty")
diff --git a/src/orb/domain/base/exceptions.py b/src/orb/domain/base/exceptions.py
index b0d9b6278..97bda6662 100644
--- a/src/orb/domain/base/exceptions.py
+++ b/src/orb/domain/base/exceptions.py
@@ -92,3 +92,13 @@ class QuotaError(DomainException):
class QuotaExceededError(QuotaError):
"""Raised when a specific provider quota is exceeded."""
+
+
+class ProviderContractError(DomainException):
+ """Raised when a provider violates the ProviderFulfilment contract.
+
+ Every call to ``check_hosts_status`` must return a ``CheckHostsStatusResult``
+ that includes a ``ProviderFulfilment``. If the provider omits it the
+ application layer raises this error instead of silently falling back to
+ count-based logic, which is always wrong for weighted fleets.
+ """
diff --git a/src/orb/domain/base/follow_up_context.py b/src/orb/domain/base/follow_up_context.py
new file mode 100644
index 000000000..7c4c16b9f
--- /dev/null
+++ b/src/orb/domain/base/follow_up_context.py
@@ -0,0 +1,73 @@
+"""Typed follow-up context for provider operations that require background tracking.
+
+``FollowUpContext`` describes *what* kind of follow-up is needed after a provider
+operation returns ``RequiresFollowUp``. These are pure value objects — frozen
+dataclasses with literal discriminants and primitive fields — and therefore
+belong in the domain layer alongside ``OperationOutcome``.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from datetime import datetime
+from typing import Literal
+
+# ---------------------------------------------------------------------------
+# Individual context variants
+# ---------------------------------------------------------------------------
+
+
+@dataclass(frozen=True)
+class TerminationFollowUpContext:
+ """Context for a return-machines follow-up.
+
+ The provider accepted the termination request but instances are still in
+ ``shutting-down`` state. The background poller should wait until all
+ ``pending_instance_ids`` reach ``terminated`` before closing the request.
+
+ Attributes:
+ follow_up_kind: Discriminator tag (always ``"termination"``).
+ pending_instance_ids: Instance IDs still being terminated.
+ expected_terminal_state: State that signals completion (``"terminated"``
+ for EC2, ``"deleted"`` for other providers).
+ poll_after: Earliest wall-clock time to issue the next status check.
+ provider_handle: Optional provider-side tracking ID (e.g. a batch job
+ reference); may be ``None`` for simple TerminateInstances calls.
+ """
+
+ follow_up_kind: Literal["termination"] = field(default="termination", init=False)
+ pending_instance_ids: list[str] = field(default_factory=list)
+ expected_terminal_state: str = "terminated"
+ poll_after: datetime | None = None
+ provider_handle: str | None = None
+
+
+@dataclass(frozen=True)
+class DeploymentPollingFollowUpContext:
+ """Context for an acquire follow-up where the fleet is still initialising.
+
+ The provider accepted the launch request. The background poller should
+ check the fleet/spot-request until all instances are ``running`` or a
+ terminal failure state is reached.
+
+ Attributes:
+ follow_up_kind: Discriminator tag (always ``"deployment_polling"``).
+ pending_resource_ids: Fleet/request IDs still being fulfilled.
+ expected_terminal_state: State that signals successful completion
+ (default ``"running"``).
+ poll_after: Earliest wall-clock time to issue the next status check.
+ provider_handle: Provider-side tracking ID (fleet request ID, etc.).
+ """
+
+ follow_up_kind: Literal["deployment_polling"] = field(default="deployment_polling", init=False)
+ pending_resource_ids: list[str] = field(default_factory=list)
+ expected_terminal_state: str = "running"
+ poll_after: datetime | None = None
+ provider_handle: str | None = None
+
+
+# ---------------------------------------------------------------------------
+# Union type
+# ---------------------------------------------------------------------------
+
+FollowUpContext = TerminationFollowUpContext | DeploymentPollingFollowUpContext
diff --git a/src/orb/domain/base/operation_outcome.py b/src/orb/domain/base/operation_outcome.py
new file mode 100644
index 000000000..495e2b858
--- /dev/null
+++ b/src/orb/domain/base/operation_outcome.py
@@ -0,0 +1,106 @@
+"""Typed discriminated union for provider operation outcomes.
+
+This module defines the ``OperationOutcome`` type — a pure domain concept that
+replaces stringly-typed ``provider_metadata`` keys with explicit, exhaustively-
+matchable variants. All variants are frozen dataclasses so they are
+value-comparable and safe to store in caches or queues.
+
+Usage example (exhaustive match with ``assert_never``)::
+
+ from typing import assert_never
+ from orb.domain.base.operation_outcome import (
+ Accepted, Completed, Failed, OperationOutcome, RequiresFollowUp,
+ )
+
+ def handle(outcome: OperationOutcome) -> str:
+ match outcome:
+ case Accepted(request_id=rid):
+ return f"accepted, tracking {rid}"
+ case Completed(resource_ids=ids):
+ return f"done: {ids}"
+ case RequiresFollowUp(context=ctx):
+ return f"follow-up needed: {ctx.follow_up_kind}"
+ case Failed(error=msg):
+ return f"failed: {msg}"
+ case _ as unreachable:
+ assert_never(unreachable)
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import Any
+
+from orb.domain.base.follow_up_context import FollowUpContext
+
+
+@dataclass(frozen=True)
+class Accepted:
+ """The provider accepted the request and is processing it asynchronously.
+
+ This is the normal AWS outcome for ``acquire`` and ``return_machines``:
+ the API call succeeded but instances are ``pending`` / ``shutting-down``.
+ Callers must poll via ``get_status`` until a terminal outcome is reached.
+
+ Attributes:
+ request_id: Provider-side tracking identifier (e.g. EC2Fleet request ID).
+ pending_resource_ids: Resource/instance IDs in a non-terminal state.
+ metadata: Optional provider-specific supplementary data.
+ """
+
+ request_id: str
+ pending_resource_ids: list[str] = field(default_factory=list)
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class Completed:
+ """The operation reached a terminal successful state.
+
+ All requested resources exist and are in their expected final state
+ (``running`` for acquire, ``terminated`` for return).
+
+ Attributes:
+ resource_ids: IDs of the resources in their terminal state.
+ metadata: Optional provider-specific supplementary data.
+ """
+
+ resource_ids: list[str] = field(default_factory=list)
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class RequiresFollowUp:
+ """The provider acknowledged the request but a background follow-up is needed.
+
+ Used when the operation cannot be completed in a single synchronous call and
+ requires a specific follow-up action beyond simple polling (e.g. a webhook
+ callback, a secondary API call, or a domain-side state machine transition).
+
+ Attributes:
+ context: Typed descriptor of *what* follow-up is required and *how*.
+ metadata: Optional provider-specific supplementary data.
+ """
+
+ context: FollowUpContext
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class Failed:
+ """The operation failed either permanently or transiently.
+
+ Attributes:
+ error: Human-readable error description.
+ recoverable: ``True`` if a retry may succeed (e.g. throttle); ``False``
+ for hard failures (e.g. invalid configuration).
+ metadata: Optional provider-specific supplementary data.
+ """
+
+ error: str
+ recoverable: bool = False
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+
+# Discriminated union — use ``match`` / ``isinstance`` for exhaustive dispatch.
+OperationOutcome = Accepted | Completed | RequiresFollowUp | Failed
diff --git a/src/orb/domain/base/ports/provider_cli_spec_port.py b/src/orb/domain/base/ports/provider_cli_spec_port.py
index b6865afcf..67db2bc3e 100644
--- a/src/orb/domain/base/ports/provider_cli_spec_port.py
+++ b/src/orb/domain/base/ports/provider_cli_spec_port.py
@@ -1,4 +1,4 @@
-"""Protocol and registry for provider CLI argument specifications."""
+"""Protocol defining how a provider exposes itself to the CLI."""
import argparse
from typing import Any, Protocol, runtime_checkable
@@ -31,24 +31,3 @@ def generate_name(self, args: argparse.Namespace) -> str: # type: ignore[return
def format_display(self, config: dict[str, Any]) -> list[tuple[str, str]]: # type: ignore[return]
"""Return a list of (label, value) pairs for display."""
pass
-
-
-class CLISpecRegistry:
- """Simple registry mapping provider type strings to ProviderCLISpecPort instances."""
-
- _specs: dict[str, ProviderCLISpecPort] = {}
-
- @classmethod
- def register(cls, provider_type: str, spec: ProviderCLISpecPort) -> None:
- """Register a CLI spec for a provider type."""
- cls._specs[provider_type] = spec
-
- @classmethod
- def get(cls, provider_type: str) -> ProviderCLISpecPort | None:
- """Return the CLI spec for a provider type, or None if not registered."""
- return cls._specs.get(provider_type)
-
- @classmethod
- def all(cls) -> dict[str, ProviderCLISpecPort]:
- """Return all registered CLI specs."""
- return dict(cls._specs)
diff --git a/src/orb/domain/base/ports/provider_defaults_loader_port.py b/src/orb/domain/base/ports/provider_defaults_loader_port.py
new file mode 100644
index 000000000..885add913
--- /dev/null
+++ b/src/orb/domain/base/ports/provider_defaults_loader_port.py
@@ -0,0 +1,26 @@
+"""Protocol for per-provider defaults loaders."""
+
+from __future__ import annotations
+
+from typing import Protocol, runtime_checkable
+
+
+@runtime_checkable
+class ProviderDefaultsLoaderPort(Protocol):
+ """Contract for a loader that returns provider-contributed config defaults.
+
+ Implementations read a provider's bundled defaults file (or any other
+ source) and return a raw config dict in the same shape as
+ ``default_config.json``. The loader is registered once per provider type
+ in ``DefaultsLoaderRegistry`` so that ``ConfigurationLoader`` can iterate
+ all registered loaders without knowing concrete provider classes.
+ """
+
+ def load_defaults(self) -> dict: # type: ignore[return]
+ """Return the provider's contributed config defaults.
+
+ Returns:
+ Raw configuration dictionary (same shape as ``default_config.json``).
+ Return an empty dict if the provider has no defaults to contribute.
+ """
+ ...
diff --git a/src/orb/domain/base/ports/provider_registry_port.py b/src/orb/domain/base/ports/provider_registry_port.py
index 459aac307..21d13b2d6 100644
--- a/src/orb/domain/base/ports/provider_registry_port.py
+++ b/src/orb/domain/base/ports/provider_registry_port.py
@@ -83,3 +83,11 @@ def create_strategy_by_type(self, provider_type: str, config: Any = None) -> Any
def create_validator(self, provider_type: str) -> Optional[Any]:
"""Create a template validator using the registered factory for the given provider type."""
pass # type: ignore[return]
+
+ @abstractmethod
+ def get_default_api(self, provider_type: str) -> Optional[str]:
+ """Return the default API name contributed by the given provider type's registration.
+
+ Returns None if the provider type is not registered or has no default API.
+ """
+ pass # type: ignore[return]
diff --git a/src/orb/domain/base/provider_fulfilment.py b/src/orb/domain/base/provider_fulfilment.py
new file mode 100644
index 000000000..2063fe7bd
--- /dev/null
+++ b/src/orb/domain/base/provider_fulfilment.py
@@ -0,0 +1,70 @@
+"""Provider fulfilment contract — domain value objects.
+
+Every call to ``check_hosts_status`` on a provider handler MUST return a
+``CheckHostsStatusResult`` that includes a ``ProviderFulfilment``. The
+application layer trusts this verdict exclusively — no count math, no
+provider-specific key inspection.
+
+See design doc: .claude/plans/PLAN-provider-fulfilment-contract.md
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Literal
+
+# The four possible states a provider can report for a request.
+#
+# fulfilled — target capacity met, all instances running, no failures.
+# in_progress — still provisioning; poll again.
+# partial — some capacity met but the provider has given up on the rest
+# (e.g. instant fleet returned fewer instances than requested).
+# failed — provider could not fulfil the request at all.
+FulfilmentState = Literal["fulfilled", "in_progress", "partial", "failed"]
+
+
+@dataclass(frozen=True)
+class ProviderFulfilment:
+ """Provider-computed verdict on whether a request is fulfilled.
+
+ Each provider knows its own API semantics (capacity units, instance
+ count, weighted vCPU, native fulfilment state machines). This object
+ is the single contract the application layer trusts — no count math
+ or provider-specific keys in shared services.
+
+ Attributes:
+ state: High-level verdict (see FulfilmentState).
+ message: Human-readable summary for status display.
+ target_units: Capacity units requested (may differ from instance count
+ for weighted fleets/ASGs). None for providers without the concept.
+ fulfilled_units: Capacity units currently fulfilled. None if unknown.
+ running_count: Number of instances in the running state. None if unknown.
+ pending_count: Number of instances still starting/pending. None if unknown.
+ failed_count: Number of instances in a failure state. None if unknown.
+ """
+
+ state: FulfilmentState
+ message: str
+ target_units: int | None = None
+ fulfilled_units: int | None = None
+ running_count: int | None = None
+ pending_count: int | None = None
+ failed_count: int | None = None
+
+
+@dataclass(frozen=True)
+class CheckHostsStatusResult:
+ """Combined result of a ``check_hosts_status`` handler call.
+
+ Bundles the per-instance details list (existing API surface) with the
+ provider-computed fulfilment verdict so callers never need to re-derive
+ fulfilment from raw instance counts.
+
+ Attributes:
+ instances: List of per-instance detail dicts (same format as the
+ previous ``list[dict]`` return type from each handler).
+ fulfilment: Provider's verdict on whether the request is fulfilled.
+ """
+
+ instances: list[dict]
+ fulfilment: ProviderFulfilment
diff --git a/src/orb/domain/base/provider_interfaces.py b/src/orb/domain/base/provider_interfaces.py
index 42d5d04c4..4fce9b406 100644
--- a/src/orb/domain/base/provider_interfaces.py
+++ b/src/orb/domain/base/provider_interfaces.py
@@ -1,18 +1,10 @@
"""Provider interfaces for domain layer - comprehensive provider abstraction."""
from dataclasses import dataclass
-from enum import Enum
+from enum import Enum # retained for ProviderInstanceState
from typing import Optional, Protocol
-class ProviderType(str, Enum):
- """Supported provider types."""
-
- AWS = "aws"
- PROVIDER1 = "provider1"
- Provider2 = "provider2"
-
-
class ProviderInstanceState(str, Enum):
"""Provider-agnostic instance states."""
@@ -51,7 +43,7 @@ def __post_init__(self) -> None:
class ProviderResourceIdentifier:
"""Provider-agnostic resource identifier."""
- provider_type: ProviderType
+ provider_type: str # e.g. "aws"
resource_type: str # e.g., "instance", "volume", "network"
identifier: str # Provider-specific ID
region: Optional[str] = None
@@ -109,8 +101,8 @@ class ProviderAdapter(Protocol):
"""Main provider adapter interface."""
@property
- def provider_type(self) -> ProviderType:
- """Get the provider type."""
+ def provider_type(self) -> str:
+ """Get the provider type (e.g. ``"aws"``)."""
...
@property
@@ -140,10 +132,10 @@ def create_launch_template(
class ProviderAdapterFactory(Protocol):
"""Factory for creating provider adapters."""
- def create_adapter(self, provider_type: ProviderType) -> ProviderAdapter:
- """Create a provider adapter for the specified type."""
+ def create_adapter(self, provider_type: str) -> ProviderAdapter:
+ """Create a provider adapter for the specified type (e.g. ``"aws"``)."""
...
- def get_supported_providers(self) -> list[ProviderType]:
- """Get list of supported provider types."""
+ def get_supported_providers(self) -> list[str]:
+ """Get list of supported provider type names."""
...
diff --git a/src/orb/domain/request/aggregate.py b/src/orb/domain/request/aggregate.py
index 0de145424..562060ad1 100644
--- a/src/orb/domain/request/aggregate.py
+++ b/src/orb/domain/request/aggregate.py
@@ -243,6 +243,27 @@ def update_metadata(self, updates: dict) -> "Request":
fields["version"] = self.version + 1
return Request.model_validate(fields)
+ def with_last_fulfilment(self, fulfilment_dict: dict) -> "Request":
+ """Store a serialized ProviderFulfilment snapshot in metadata.
+
+ The snapshot is written under the key ``last_fulfilment``. It is a
+ plain dict so it survives round-trips through any persistence layer.
+ The value is provider-agnostic — ProviderFulfilment is a domain value
+ object — so storing it here violates no domain boundary.
+
+ Args:
+ fulfilment_dict: Result of ``ProviderFulfilment``-like ``__dict__``
+ or ``dataclasses.asdict`` — a plain serialisable dict.
+
+ Returns:
+ Updated Request with ``metadata["last_fulfilment"]`` set.
+ """
+ new_metadata = {**self.metadata, "last_fulfilment": fulfilment_dict}
+ fields = self.model_dump()
+ fields["metadata"] = new_metadata
+ fields["version"] = self.version + 1
+ return Request.model_validate(fields)
+
def with_launch_template_info(self, template_id: str, version: str) -> "Request":
new_provider_data = {
**self.provider_data,
diff --git a/src/orb/domain/template/extensions.py b/src/orb/domain/template/extensions.py
index 0fb5ef99b..d91d813d5 100644
--- a/src/orb/domain/template/extensions.py
+++ b/src/orb/domain/template/extensions.py
@@ -1,9 +1,7 @@
-"""Template extension registry for provider-specific extensions."""
+"""Abstract base for provider-specific template extensions."""
from abc import ABC, abstractmethod
-from typing import Any, Optional
-
-from pydantic import BaseModel
+from typing import Any
class TemplateExtension(ABC):
@@ -16,159 +14,3 @@ def to_template_defaults(self) -> dict[str, Any]:
@abstractmethod
def get_provider_type(self) -> str:
"""Get the provider type this extension supports."""
-
-
-class TemplateExtensionRegistry:
- """Registry for provider-specific template extensions.
-
- This registry follows the Open/Closed Principle - it's open for extension
- (new providers can be added) but closed for modification (existing code
- doesn't need to change when new providers are added).
- """
-
- _extensions: dict[str, type[BaseModel]] = {}
- _extension_instances: dict[str, TemplateExtension] = {}
-
- @classmethod
- def register_extension(cls, provider_type: str, extension_class: type[BaseModel]) -> None:
- """Register a provider-specific extension configuration class.
-
- Args:
- provider_type: The provider type (e.g., 'aws', 'provider1', 'provider2')
- extension_class: The Pydantic model class for the extension configuration
- """
- if not issubclass(extension_class, BaseModel):
- raise ValueError(f"Extension class must be a Pydantic BaseModel, got {extension_class}")
-
- cls._extensions[provider_type] = extension_class
-
- @classmethod
- def register_extension_instance(
- cls, provider_type: str, extension_instance: TemplateExtension
- ) -> None:
- """Register a provider-specific extension instance.
-
- Args:
- provider_type: The provider type (e.g., 'aws', 'provider1', 'provider2')
- extension_instance: The extension instance implementing TemplateExtension
- """
- if not isinstance(extension_instance, TemplateExtension):
- raise ValueError(
- f"Extension instance must implement TemplateExtension, got {type(extension_instance)}"
- )
-
- cls._extension_instances[provider_type] = extension_instance
-
- @classmethod
- def get_extension_class(cls, provider_type: str) -> Optional[type[BaseModel]]:
- """Get extension configuration class for a provider.
-
- Args:
- provider_type: The provider type to get extension class for
-
- Returns:
- The extension configuration class or None if not registered
- """
- return cls._extensions.get(provider_type)
-
- @classmethod
- def get_extension_instance(cls, provider_type: str) -> Optional[TemplateExtension]:
- """Get extension instance for a provider.
-
- Args:
- provider_type: The provider type to get extension instance for
-
- Returns:
- The extension instance or None if not registered
- """
- return cls._extension_instances.get(provider_type)
-
- @classmethod
- def has_extension(cls, provider_type: str) -> bool:
- """Check if a provider has registered extensions.
-
- Args:
- provider_type: The provider type to check
-
- Returns:
- True if the provider has registered extensions
- """
- return provider_type in cls._extensions or provider_type in cls._extension_instances
-
- @classmethod
- def get_supported_providers(cls) -> list[str]:
- """Get list of providers with registered extensions.
-
- Returns:
- List of provider types that have registered extensions
- """
- all_providers = set(cls._extensions.keys()) | set(cls._extension_instances.keys())
- return list(all_providers)
-
- @classmethod
- def create_extension_config(
- cls, provider_type: str, config_data: dict[str, Any]
- ) -> Optional[BaseModel]:
- """Create an extension configuration instance for a provider.
-
- Args:
- provider_type: The provider type
- config_data: The configuration data to create the extension with
-
- Returns:
- The created extension configuration instance or None if provider not registered
- """
- extension_class = cls.get_extension_class(provider_type)
- if extension_class:
- return extension_class(**config_data)
- return None
-
- @classmethod
- def get_extension_defaults(
- cls, provider_type: str, config_data: Optional[dict[str, Any]] = None
- ) -> dict[str, Any]:
- """Get template defaults from provider extension.
-
- Args:
- provider_type: The provider type
- config_data: Optional configuration data to create extension with
-
- Returns:
- Dictionary of template defaults from the extension
- """
- # Try extension instance first
- extension_instance = cls.get_extension_instance(provider_type)
- if extension_instance:
- return extension_instance.to_template_defaults() # type: ignore[attr-defined]
-
- # Try creating from extension class
- if config_data:
- extension_config = cls.create_extension_config(provider_type, config_data)
- if extension_config and hasattr(extension_config, "to_template_defaults"):
- return extension_config.to_template_defaults() # type: ignore[attr-defined]
-
- return {}
-
- @classmethod
- def clear_registry(cls) -> None:
- """Clear all registered extensions (mainly for testing)."""
- cls._extensions.clear()
- cls._extension_instances.clear()
-
-
-# Convenience functions for common operations
-def register_provider_extension(provider_type: str, extension_class: type[BaseModel]) -> None:
- """Register a provider extension."""
- TemplateExtensionRegistry.register_extension(provider_type, extension_class)
-
-
-def get_provider_extension_defaults(
- provider_type: str, config_data: Optional[dict[str, Any]] = None
-) -> dict[str, Any]:
- """Get provider extension defaults."""
- return TemplateExtensionRegistry.get_extension_defaults(provider_type, config_data)
-
-
-def has_provider_extension(provider_type: str) -> bool:
- """Check if provider has extensions."""
- return TemplateExtensionRegistry.has_extension(provider_type)
diff --git a/src/orb/domain/template/factory.py b/src/orb/domain/template/factory.py
index 746bd6d2d..2837ee71b 100644
--- a/src/orb/domain/template/factory.py
+++ b/src/orb/domain/template/factory.py
@@ -4,7 +4,6 @@
from typing import Any, Optional, Protocol
from orb.domain.base.ports.logging_port import LoggingPort
-from orb.domain.template.extensions import TemplateExtensionRegistry
from orb.domain.template.template_aggregate import Template
@@ -49,16 +48,16 @@ class TemplateFactory(BaseTemplateFactory):
def __init__(
self,
- extension_registry: Optional[TemplateExtensionRegistry] = None,
+ extension_registry: Optional[Any] = None,
logger: Optional[LoggingPort] = None,
) -> None:
"""Initialize template factory.
Args:
- extension_registry: Registry for provider extensions (defaults to global registry)
+ extension_registry: Registry for provider extensions (injected by infrastructure layer)
logger: Logger port for logging factory operations
"""
- self._extension_registry = extension_registry or TemplateExtensionRegistry
+ self._extension_registry = extension_registry
self._logger = logger
# Registry of provider-specific template classes
@@ -192,7 +191,11 @@ def create_template_with_extensions(
merged_data = template_data.copy()
# Apply extension defaults from registry
- if provider_type and self._extension_registry.has_extension(provider_type):
+ if (
+ provider_type
+ and self._extension_registry is not None
+ and self._extension_registry.has_extension(provider_type)
+ ):
extension_defaults = self._extension_registry.get_extension_defaults(
provider_type, extension_data
)
diff --git a/src/orb/domain/template/ports/template_extension_registry_port.py b/src/orb/domain/template/ports/template_extension_registry_port.py
new file mode 100644
index 000000000..1f0b55cc2
--- /dev/null
+++ b/src/orb/domain/template/ports/template_extension_registry_port.py
@@ -0,0 +1,34 @@
+"""Port for provider template extension registries."""
+
+from __future__ import annotations
+
+from typing import Any, Optional, Protocol, runtime_checkable
+
+
+@runtime_checkable
+class TemplateExtensionRegistryPort(Protocol):
+ """Contract for a registry that provides provider extension defaults.
+
+ The application layer depends only on this protocol — not on the concrete
+ registry implementation — keeping the application→infrastructure boundary clean.
+ Implementations may delegate to class-level state (e.g. the global
+ ``TemplateExtensionRegistry``) via an adapter registered in DI.
+ """
+
+ def get_extension_defaults(
+ self,
+ provider_type: str,
+ config_data: Optional[dict[str, Any]] = None,
+ ) -> dict[str, Any]: # type: ignore[return]
+ """Return template defaults contributed by the named provider extension.
+
+ Args:
+ provider_type: Provider type string (e.g. ``'aws'``).
+ config_data: Optional per-instance configuration data that may be
+ used to instantiate an extension class on demand.
+
+ Returns:
+ Mapping of template field names to default values. Returns an
+ empty dict when no extension is registered for *provider_type*.
+ """
+ ...
diff --git a/src/orb/infrastructure/auth/registry.py b/src/orb/infrastructure/auth/registry.py
index de9823d12..ef4f76858 100644
--- a/src/orb/infrastructure/auth/registry.py
+++ b/src/orb/infrastructure/auth/registry.py
@@ -35,7 +35,8 @@ def register_strategy(
Args:
strategy_name: Name of the strategy (e.g., 'none', 'bearer_token', 'oauth')
- strategy_factory: Factory function that creates the strategy instance
+ strategy_factory: Factory callable that creates the strategy instance.
+ Must expose a ``from_auth_config(auth_config)`` classmethod.
"""
# Create a simple config factory that passes through kwargs
@@ -44,13 +45,16 @@ def config_factory(**kwargs):
self.register_type(strategy_name, strategy_factory, config_factory)
- def get_strategy(self, strategy_name: str, **kwargs) -> AuthPort:
+ def get_strategy(self, strategy_name: str, auth_config: Any) -> AuthPort:
"""
- Get an authentication strategy instance.
+ Get an authentication strategy instance built from *auth_config*.
+
+ Internally delegates to ``strategy_factory.from_auth_config(auth_config)``
+ so that each strategy class owns its own config-extraction logic.
Args:
- strategy_name: Name of the strategy
- **kwargs: Arguments to pass to the strategy factory
+ strategy_name: Name of the registered strategy
+ auth_config: AuthConfig instance passed to ``from_auth_config``
Returns:
Authentication strategy instance
@@ -59,7 +63,7 @@ def get_strategy(self, strategy_name: str, **kwargs) -> AuthPort:
ValueError: If strategy is not registered
"""
registration = self._get_type_registration(strategy_name)
- return registration.strategy_factory(**kwargs)
+ return registration.strategy_factory.from_auth_config(auth_config) # type: ignore[union-attr]
def list_strategies(self) -> list[str]:
"""
@@ -98,3 +102,8 @@ def _register_default_strategies(registry: AuthRegistry) -> None: # type: ignor
from .strategy.bearer_token_strategy import BearerTokenStrategy
registry.register_strategy("bearer_token", BearerTokenStrategy)
+
+ # Register enhanced bearer token strategy
+ from .strategy.bearer_token_strategy_enhanced import EnhancedBearerTokenStrategy
+
+ registry.register_strategy("bearer_token_enhanced", EnhancedBearerTokenStrategy)
diff --git a/src/orb/infrastructure/auth/strategy/bearer_token_strategy.py b/src/orb/infrastructure/auth/strategy/bearer_token_strategy.py
index 5d7d8745a..f2fdf00b7 100644
--- a/src/orb/infrastructure/auth/strategy/bearer_token_strategy.py
+++ b/src/orb/infrastructure/auth/strategy/bearer_token_strategy.py
@@ -1,9 +1,13 @@
"""Bearer token authentication strategy."""
+from __future__ import annotations
+
import time
+from typing import TYPE_CHECKING, Any
import jwt
+from orb.domain.base.exceptions import ConfigurationError
from orb.infrastructure.adapters.ports.auth import (
AuthContext,
AuthPort,
@@ -12,6 +16,9 @@
)
from orb.infrastructure.logging.logger import get_logger
+if TYPE_CHECKING:
+ pass
+
class BearerTokenStrategy(AuthPort):
"""Authentication strategy using Bearer tokens (JWT)."""
@@ -177,10 +184,10 @@ async def refresh_token(self, refresh_token: str) -> AuthResult:
async def revoke_token(self, token: str) -> bool:
"""
- Revoke token (add to blacklist).
+ Revoke token (add to denylist).
Note: This is a simplified implementation. In production,
- you would maintain a token blacklist in a database or cache.
+ you would maintain a token denylist in a database or cache.
Args:
token: Token to revoke
@@ -193,6 +200,45 @@ async def revoke_token(self, token: str) -> bool:
"Use EnhancedBearerTokenStrategy for revocation support."
)
+ @classmethod
+ def from_auth_config(cls, auth_config: Any) -> BearerTokenStrategy:
+ """
+ Build strategy instance from AuthConfig.
+
+ Extracts and validates the bearer_token sub-config, then constructs
+ a ``BearerTokenStrategy``.
+
+ Args:
+ auth_config: AuthConfig instance with typed bearer_token sub-config
+
+ Returns:
+ Configured BearerTokenStrategy
+
+ Raises:
+ ConfigurationError: If required fields are missing or invalid
+ """
+ bearer_cfg = getattr(auth_config, "bearer_token", None)
+ if bearer_cfg is None:
+ raise ConfigurationError(
+ "Bearer token authentication requires auth.bearer_token configuration. "
+ "Set auth.bearer_token.secret_key in your server config."
+ )
+ secret_key: str = getattr(bearer_cfg, "secret_key", "") or ""
+ if not secret_key:
+ raise ConfigurationError(
+ "Bearer token authentication requires a secret_key in auth.bearer_token config."
+ )
+ if len(secret_key.encode()) < 32:
+ raise ConfigurationError(
+ "Bearer token secret_key must be at least 32 bytes for security."
+ )
+ return cls(
+ secret_key=secret_key,
+ algorithm=getattr(bearer_cfg, "algorithm", "HS256"),
+ token_expiry=getattr(bearer_cfg, "token_expiry", 3600),
+ enabled=True,
+ )
+
def get_strategy_name(self) -> str:
"""
Get strategy name.
diff --git a/src/orb/infrastructure/auth/strategy/bearer_token_strategy_enhanced.py b/src/orb/infrastructure/auth/strategy/bearer_token_strategy_enhanced.py
index 0bb392599..361c33c75 100644
--- a/src/orb/infrastructure/auth/strategy/bearer_token_strategy_enhanced.py
+++ b/src/orb/infrastructure/auth/strategy/bearer_token_strategy_enhanced.py
@@ -1,21 +1,28 @@
-"""Enhanced bearer token authentication strategy with blacklist and rate limiting."""
+"""Enhanced bearer token authentication strategy with denylist and rate limiting."""
+
+from __future__ import annotations
import base64
import json
import time
from collections import defaultdict
+from typing import TYPE_CHECKING, Any
import jwt
+from orb.domain.base.exceptions import ConfigurationError
from orb.infrastructure.adapters.ports.auth import (
AuthContext,
AuthPort,
AuthResult,
AuthStatus,
)
-from orb.infrastructure.auth.token_blacklist import TokenBlacklistPort
+from orb.infrastructure.auth.token_denylist import TokenDenylistPort
from orb.infrastructure.logging.logger import get_logger
+if TYPE_CHECKING:
+ pass
+
class RateLimiter:
"""Simple in-memory rate limiter for token validation.
@@ -68,12 +75,12 @@ def is_rate_limited(self, identifier: str) -> bool:
class EnhancedBearerTokenStrategy(AuthPort):
- """Enhanced authentication strategy with JWT blacklist and rate limiting."""
+ """Enhanced authentication strategy with JWT denylist and rate limiting."""
def __init__(
self,
secret_key: str,
- blacklist: TokenBlacklistPort,
+ denylist: TokenDenylistPort,
algorithm: str = "HS256",
token_expiry: int = 3600,
enabled: bool = True,
@@ -86,7 +93,7 @@ def __init__(
Args:
secret_key: Secret key for JWT signing/verification
- blacklist: Token blacklist implementation
+ denylist: Token denylist implementation
algorithm: JWT algorithm to use
token_expiry: Token expiry time in seconds
enabled: Whether this strategy is enabled
@@ -95,7 +102,7 @@ def __init__(
rate_window: Rate limit window in seconds
"""
self.secret_key = secret_key
- self.blacklist = blacklist
+ self.denylist = denylist
self.algorithm = algorithm
self.token_expiry = token_expiry
self.enabled = enabled
@@ -105,8 +112,8 @@ def __init__(
# Validate secret key strength (minimum 256 bits = 32 bytes)
if len(secret_key.encode()) < 32:
- self.logger.warning(
- "Secret key is weak (< 256 bits). Use a stronger key for production."
+ raise ConfigurationError(
+ "Bearer token secret_key must be at least 32 bytes for security."
)
async def authenticate(self, context: AuthContext) -> AuthResult:
@@ -157,7 +164,7 @@ async def authenticate(self, context: AuthContext) -> AuthResult:
async def validate_token(self, token: str) -> AuthResult:
"""
- Validate JWT token with blacklist check.
+ Validate JWT token with denylist check.
Args:
token: JWT token to validate
@@ -166,8 +173,8 @@ async def validate_token(self, token: str) -> AuthResult:
Authentication result with user information
"""
try:
- # Check blacklist first (fail fast)
- if await self.blacklist.is_blacklisted(token):
+ # Check denylist first (fail fast)
+ if await self.denylist.is_denylisted(token):
self.logger.warning("Attempted use of revoked JWT")
return AuthResult(
status=AuthStatus.INVALID,
@@ -233,8 +240,8 @@ async def refresh_token(self, refresh_token: str) -> AuthResult:
New authentication result with fresh token
"""
try:
- # Check blacklist
- if await self.blacklist.is_blacklisted(refresh_token):
+ # Check denylist
+ if await self.denylist.is_denylisted(refresh_token):
return AuthResult(
status=AuthStatus.INVALID,
error_message="Refresh token has been revoked",
@@ -281,7 +288,7 @@ async def refresh_token(self, refresh_token: str) -> AuthResult:
async def revoke_token(self, token: str) -> bool:
"""
- Revoke token by adding to blacklist.
+ Revoke token by adding to denylist.
Args:
token: Token to revoke
@@ -291,7 +298,7 @@ async def revoke_token(self, token: str) -> bool:
"""
try:
# Extract expiration from JWT payload without verification
- # (token is being revoked, we only need exp for blacklist TTL)
+ # (token is being revoked, we only need exp for denylist TTL)
try:
payload_part = token.split(".")[1]
# Add padding if needed
@@ -303,13 +310,13 @@ async def revoke_token(self, token: str) -> bool:
except Exception:
expires_at = None
- # Add to blacklist
- success = await self.blacklist.add_token(token, expires_at)
+ # Add to denylist
+ success = await self.denylist.add_token(token, expires_at)
if success:
- self.logger.info("JWT revoked and added to blacklist")
+ self.logger.info("JWT revoked and added to denylist")
else:
- self.logger.error("Failed to add JWT to blacklist")
+ self.logger.error("Failed to add JWT to denylist")
return success
@@ -317,6 +324,44 @@ async def revoke_token(self, token: str) -> bool:
self.logger.error("Auth revocation error: %s", e)
return False
+ @classmethod
+ def from_auth_config(cls, auth_config: Any) -> EnhancedBearerTokenStrategy:
+ """
+ Build strategy instance from AuthConfig.
+
+ Extracts the bearer_token sub-config and wires an InMemoryTokenDenylist.
+
+ Args:
+ auth_config: AuthConfig instance with typed bearer_token sub-config
+
+ Returns:
+ Configured EnhancedBearerTokenStrategy
+
+ Raises:
+ ConfigurationError: If required fields are missing or invalid
+ """
+ from orb.infrastructure.auth.token_denylist.in_memory_denylist import (
+ InMemoryTokenDenylist,
+ )
+
+ bearer_cfg = getattr(auth_config, "bearer_token", None)
+ if bearer_cfg is None:
+ raise ConfigurationError(
+ "Enhanced bearer token authentication requires auth.bearer_token configuration."
+ )
+ secret_key: str = getattr(bearer_cfg, "secret_key", "") or ""
+ if not secret_key:
+ raise ConfigurationError(
+ "Enhanced bearer token authentication requires a secret_key in auth.bearer_token config."
+ )
+ return cls(
+ secret_key=secret_key,
+ denylist=InMemoryTokenDenylist(),
+ algorithm=getattr(bearer_cfg, "algorithm", "HS256"),
+ token_expiry=getattr(bearer_cfg, "token_expiry", 3600),
+ enabled=True,
+ )
+
def get_strategy_name(self) -> str:
"""Get strategy name."""
return "enhanced_bearer_token"
diff --git a/src/orb/infrastructure/auth/strategy/no_auth_strategy.py b/src/orb/infrastructure/auth/strategy/no_auth_strategy.py
index e238802b1..4b0124b76 100644
--- a/src/orb/infrastructure/auth/strategy/no_auth_strategy.py
+++ b/src/orb/infrastructure/auth/strategy/no_auth_strategy.py
@@ -1,5 +1,9 @@
"""No authentication strategy - allows all requests."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
from orb.infrastructure.adapters.ports.auth import (
AuthContext,
AuthPort,
@@ -8,6 +12,9 @@
)
from orb.infrastructure.logging.logger import get_logger
+if TYPE_CHECKING:
+ pass
+
class NoAuthStrategy(AuthPort):
"""Authentication strategy that allows all requests without authentication."""
@@ -22,6 +29,19 @@ def __init__(self, enabled: bool = False) -> None:
self.enabled = enabled
self.logger = get_logger(__name__)
+ @classmethod
+ def from_auth_config(cls, auth_config: Any) -> NoAuthStrategy:
+ """
+ Build strategy instance from AuthConfig.
+
+ Args:
+ auth_config: AuthConfig instance (ignored for no-auth)
+
+ Returns:
+ NoAuthStrategy with enabled=False
+ """
+ return cls(enabled=False)
+
async def authenticate(self, context: AuthContext) -> AuthResult:
"""
Allow all requests without authentication.
diff --git a/src/orb/infrastructure/auth/token_blacklist/__init__.py b/src/orb/infrastructure/auth/token_blacklist/__init__.py
deleted file mode 100644
index 87ad2649a..000000000
--- a/src/orb/infrastructure/auth/token_blacklist/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""Token blacklist implementation for JWT revocation."""
-
-from .blacklist_port import TokenBlacklistPort
-from .in_memory_blacklist import InMemoryTokenBlacklist
-from .redis_blacklist import RedisTokenBlacklist
-
-__all__ = [
- "TokenBlacklistPort",
- "InMemoryTokenBlacklist",
- "RedisTokenBlacklist",
-]
diff --git a/src/orb/infrastructure/auth/token_denylist/__init__.py b/src/orb/infrastructure/auth/token_denylist/__init__.py
new file mode 100644
index 000000000..6778b8b11
--- /dev/null
+++ b/src/orb/infrastructure/auth/token_denylist/__init__.py
@@ -0,0 +1,11 @@
+"""Token denylist implementation for JWT revocation."""
+
+from .denylist_port import TokenDenylistPort
+from .in_memory_denylist import InMemoryTokenDenylist
+from .redis_denylist import RedisTokenDenylist
+
+__all__ = [
+ "TokenDenylistPort",
+ "InMemoryTokenDenylist",
+ "RedisTokenDenylist",
+]
diff --git a/src/orb/infrastructure/auth/token_blacklist/blacklist_port.py b/src/orb/infrastructure/auth/token_denylist/denylist_port.py
similarity index 61%
rename from src/orb/infrastructure/auth/token_blacklist/blacklist_port.py
rename to src/orb/infrastructure/auth/token_denylist/denylist_port.py
index ad3687d4f..267d57ea0 100644
--- a/src/orb/infrastructure/auth/token_blacklist/blacklist_port.py
+++ b/src/orb/infrastructure/auth/token_denylist/denylist_port.py
@@ -1,19 +1,19 @@
-"""Token blacklist port interface."""
+"""Token denylist port interface."""
from abc import ABC, abstractmethod
from typing import Optional
-class TokenBlacklistPort(ABC):
- """Port interface for token blacklist implementations."""
+class TokenDenylistPort(ABC):
+ """Port interface for token denylist implementations."""
@abstractmethod
async def add_token(self, token: str, expires_at: Optional[int] = None) -> bool:
"""
- Add token to blacklist.
+ Add token to denylist.
Args:
- token: Token to blacklist
+ token: Token to add to the denylist
expires_at: Unix timestamp when token expires (for automatic cleanup)
Returns:
@@ -21,21 +21,21 @@ async def add_token(self, token: str, expires_at: Optional[int] = None) -> bool:
"""
@abstractmethod
- async def is_blacklisted(self, token: str) -> bool:
+ async def is_denylisted(self, token: str) -> bool:
"""
- Check if token is blacklisted.
+ Check if token is on the denylist.
Args:
token: Token to check
Returns:
- True if token is blacklisted
+ True if token is on the denylist
"""
@abstractmethod
async def remove_token(self, token: str) -> bool:
"""
- Remove token from blacklist.
+ Remove token from denylist.
Args:
token: Token to remove
@@ -47,17 +47,17 @@ async def remove_token(self, token: str) -> bool:
@abstractmethod
async def cleanup_expired(self) -> int:
"""
- Remove expired tokens from blacklist.
+ Remove expired tokens from denylist.
Returns:
Number of tokens removed
"""
@abstractmethod
- async def get_blacklist_size(self) -> int:
+ async def get_denylist_size(self) -> int:
"""
- Get number of tokens in blacklist.
+ Get number of tokens on the denylist.
Returns:
- Number of blacklisted tokens
+ Number of denylisted tokens
"""
diff --git a/src/orb/infrastructure/auth/token_blacklist/in_memory_blacklist.py b/src/orb/infrastructure/auth/token_denylist/in_memory_denylist.py
similarity index 66%
rename from src/orb/infrastructure/auth/token_blacklist/in_memory_blacklist.py
rename to src/orb/infrastructure/auth/token_denylist/in_memory_denylist.py
index 6cdc3bd8d..1764687e1 100644
--- a/src/orb/infrastructure/auth/token_blacklist/in_memory_blacklist.py
+++ b/src/orb/infrastructure/auth/token_denylist/in_memory_denylist.py
@@ -1,4 +1,4 @@
-"""In-memory token blacklist implementation."""
+"""In-memory token denylist implementation."""
import asyncio
import time
@@ -6,78 +6,78 @@
from orb.infrastructure.logging.logger import get_logger
-from .blacklist_port import TokenBlacklistPort
+from .denylist_port import TokenDenylistPort
-class InMemoryTokenBlacklist(TokenBlacklistPort):
- """In-memory token blacklist with automatic cleanup."""
+class InMemoryTokenDenylist(TokenDenylistPort):
+ """In-memory token denylist with automatic cleanup."""
def __init__(self, cleanup_interval: int = 3600) -> None:
"""
- Initialize in-memory blacklist.
+ Initialize in-memory denylist.
Args:
cleanup_interval: Interval in seconds for automatic cleanup
"""
- self._blacklist: Dict[str, Optional[int]] = {}
+ self._denylist: Dict[str, Optional[int]] = {}
self._cleanup_interval = cleanup_interval
self._cleanup_task: Optional[asyncio.Task] = None
self._logger = get_logger(__name__)
self._lock = asyncio.Lock()
async def add_token(self, token: str, expires_at: Optional[int] = None) -> bool:
- """Add token to blacklist."""
+ """Add token to denylist."""
async with self._lock:
- self._blacklist[token] = expires_at
- self._logger.info("Token added to blacklist (expires_at=%s)", expires_at)
+ self._denylist[token] = expires_at
+ self._logger.info("Token added to denylist (expires_at=%s)", expires_at)
return True
- async def is_blacklisted(self, token: str) -> bool:
- """Check if token is blacklisted."""
+ async def is_denylisted(self, token: str) -> bool:
+ """Check if token is on the denylist."""
async with self._lock:
- if token not in self._blacklist:
+ if token not in self._denylist:
return False
# Check if token has expired
- expires_at = self._blacklist[token]
+ expires_at = self._denylist[token]
if expires_at and time.time() > expires_at:
- # Token expired, remove from blacklist
- del self._blacklist[token]
+ # Token expired, remove from denylist
+ del self._denylist[token]
return False
return True
async def remove_token(self, token: str) -> bool:
- """Remove token from blacklist."""
+ """Remove token from denylist."""
async with self._lock:
- if token in self._blacklist:
- del self._blacklist[token]
- self._logger.info("Token removed from blacklist")
+ if token in self._denylist:
+ del self._denylist[token]
+ self._logger.info("Token removed from denylist")
return True
return False
async def cleanup_expired(self) -> int:
- """Remove expired tokens from blacklist."""
+ """Remove expired tokens from denylist."""
async with self._lock:
current_time = time.time()
expired_tokens = [
token
- for token, expires_at in self._blacklist.items()
+ for token, expires_at in self._denylist.items()
if expires_at and current_time > expires_at
]
for token in expired_tokens:
- del self._blacklist[token]
+ del self._denylist[token]
if expired_tokens:
self._logger.info("Cleaned up %d expired tokens", len(expired_tokens))
return len(expired_tokens)
- async def get_blacklist_size(self) -> int:
- """Get number of tokens in blacklist."""
+ async def get_denylist_size(self) -> int:
+ """Get number of tokens on the denylist."""
async with self._lock:
- return len(self._blacklist)
+ return len(self._denylist)
async def start_cleanup_task(self) -> None:
"""Start automatic cleanup task."""
diff --git a/src/orb/infrastructure/auth/token_blacklist/redis_blacklist.py b/src/orb/infrastructure/auth/token_denylist/redis_denylist.py
similarity index 65%
rename from src/orb/infrastructure/auth/token_blacklist/redis_blacklist.py
rename to src/orb/infrastructure/auth/token_denylist/redis_denylist.py
index 78d5b59b2..b7d1e579b 100644
--- a/src/orb/infrastructure/auth/token_blacklist/redis_blacklist.py
+++ b/src/orb/infrastructure/auth/token_denylist/redis_denylist.py
@@ -1,19 +1,19 @@
-"""Redis-based token blacklist implementation."""
+"""Redis-based token denylist implementation."""
import time
from typing import Optional
from orb.infrastructure.logging.logger import get_logger
-from .blacklist_port import TokenBlacklistPort
+from .denylist_port import TokenDenylistPort
-class RedisTokenBlacklist(TokenBlacklistPort):
- """Redis-based token blacklist with automatic expiration."""
+class RedisTokenDenylist(TokenDenylistPort):
+ """Redis-based token denylist with automatic expiration."""
- def __init__(self, redis_client=None, key_prefix: str = "token_blacklist:") -> None:
+ def __init__(self, redis_client=None, key_prefix: str = "token_denylist:") -> None:
"""
- Initialize Redis blacklist.
+ Initialize Redis denylist.
Args:
redis_client: Redis client instance (optional, will use in-memory if None)
@@ -26,12 +26,12 @@ def __init__(self, redis_client=None, key_prefix: str = "token_blacklist:") -> N
if self._redis is None:
self._logger.warning("Redis client not provided, using in-memory fallback")
- from .in_memory_blacklist import InMemoryTokenBlacklist
+ from .in_memory_denylist import InMemoryTokenDenylist
- self._fallback = InMemoryTokenBlacklist()
+ self._fallback = InMemoryTokenDenylist()
async def add_token(self, token: str, expires_at: Optional[int] = None) -> bool:
- """Add token to blacklist."""
+ """Add token to denylist."""
if self._fallback:
return await self._fallback.add_token(token, expires_at)
@@ -46,17 +46,17 @@ async def add_token(self, token: str, expires_at: Optional[int] = None) -> bool:
# No expiration, set indefinitely
await self._redis.set(key, "1") # type: ignore[union-attr]
- self._logger.info("Token added to Redis blacklist (expires_at=%s)", expires_at)
+ self._logger.info("Token added to Redis denylist (expires_at=%s)", expires_at)
return True
except Exception as e:
- self._logger.error("Failed to add token to Redis blacklist: %s", e)
+ self._logger.error("Failed to add token to Redis denylist: %s", e)
return False
- async def is_blacklisted(self, token: str) -> bool:
- """Check if token is blacklisted."""
+ async def is_denylisted(self, token: str) -> bool:
+ """Check if token is on the denylist."""
if self._fallback:
- return await self._fallback.is_blacklisted(token)
+ return await self._fallback.is_denylisted(token)
try:
key = f"{self._key_prefix}{token}"
@@ -64,28 +64,28 @@ async def is_blacklisted(self, token: str) -> bool:
return bool(result)
except Exception as e:
- self._logger.error("Failed to check token in Redis blacklist: %s", e)
- # Fail secure: assume token is blacklisted on error
+ self._logger.error("Failed to check token in Redis denylist: %s", e)
+ # Fail secure: assume token is denylisted on error
return True
async def remove_token(self, token: str) -> bool:
- """Remove token from blacklist."""
+ """Remove token from denylist."""
if self._fallback:
return await self._fallback.remove_token(token)
try:
key = f"{self._key_prefix}{token}"
result = await self._redis.delete(key) # type: ignore[union-attr]
- self._logger.info("Token removed from Redis blacklist")
+ self._logger.info("Token removed from Redis denylist")
return bool(result)
except Exception as e:
- self._logger.error("Failed to remove token from Redis blacklist: %s", e)
+ self._logger.error("Failed to remove token from Redis denylist: %s", e)
return False
async def cleanup_expired(self) -> int:
"""
- Remove expired tokens from blacklist.
+ Remove expired tokens from denylist.
Note: Redis automatically removes expired keys, so this is a no-op.
"""
@@ -95,10 +95,10 @@ async def cleanup_expired(self) -> int:
# Redis handles expiration automatically
return 0
- async def get_blacklist_size(self) -> int:
- """Get number of tokens in blacklist."""
+ async def get_denylist_size(self) -> int:
+ """Get number of tokens on the denylist."""
if self._fallback:
- return await self._fallback.get_blacklist_size()
+ return await self._fallback.get_denylist_size()
try:
# Count keys matching our prefix
@@ -107,5 +107,5 @@ async def get_blacklist_size(self) -> int:
return len(keys)
except Exception as e:
- self._logger.error("Failed to get blacklist size from Redis: %s", e)
+ self._logger.error("Failed to get denylist size from Redis: %s", e)
return 0
diff --git a/src/orb/infrastructure/registry/cli_spec_registry.py b/src/orb/infrastructure/registry/cli_spec_registry.py
new file mode 100644
index 000000000..209fb500b
--- /dev/null
+++ b/src/orb/infrastructure/registry/cli_spec_registry.py
@@ -0,0 +1,24 @@
+"""CLI spec registry — maps provider type strings to ProviderCLISpecPort instances."""
+
+from orb.domain.base.ports.provider_cli_spec_port import ProviderCLISpecPort
+
+
+class CLISpecRegistry:
+ """Simple registry mapping provider type strings to ProviderCLISpecPort instances."""
+
+ _specs: dict[str, ProviderCLISpecPort] = {}
+
+ @classmethod
+ def register(cls, provider_type: str, spec: ProviderCLISpecPort) -> None:
+ """Register a CLI spec for a provider type."""
+ cls._specs[provider_type] = spec
+
+ @classmethod
+ def get(cls, provider_type: str) -> ProviderCLISpecPort | None:
+ """Return the CLI spec for a provider type, or None if not registered."""
+ return cls._specs.get(provider_type)
+
+ @classmethod
+ def all(cls) -> dict[str, ProviderCLISpecPort]:
+ """Return all registered CLI specs."""
+ return dict(cls._specs)
diff --git a/src/orb/infrastructure/registry/template_extension_registry.py b/src/orb/infrastructure/registry/template_extension_registry.py
new file mode 100644
index 000000000..b29cf891d
--- /dev/null
+++ b/src/orb/infrastructure/registry/template_extension_registry.py
@@ -0,0 +1,178 @@
+"""Template extension registry for provider-specific extensions."""
+
+from typing import Any, Optional
+
+from pydantic import BaseModel
+
+from orb.domain.template.extensions import TemplateExtension
+
+
+class TemplateExtensionRegistry:
+ """Registry for provider-specific template extensions.
+
+ This registry follows the Open/Closed Principle - it's open for extension
+ (new providers can be added) but closed for modification (existing code
+ doesn't need to change when new providers are added).
+ """
+
+ _extensions: dict[str, type[BaseModel]] = {}
+ _extension_instances: dict[str, TemplateExtension] = {}
+
+ @classmethod
+ def register_extension(cls, provider_type: str, extension_class: type[BaseModel]) -> None:
+ """Register a provider-specific extension configuration class.
+
+ Args:
+ provider_type: The provider type (e.g., 'aws', 'provider1', 'provider2')
+ extension_class: The Pydantic model class for the extension configuration
+ """
+ if not issubclass(extension_class, BaseModel):
+ raise ValueError(f"Extension class must be a Pydantic BaseModel, got {extension_class}")
+
+ cls._extensions[provider_type] = extension_class
+
+ @classmethod
+ def register_extension_instance(
+ cls, provider_type: str, extension_instance: TemplateExtension
+ ) -> None:
+ """Register a provider-specific extension instance.
+
+ Args:
+ provider_type: The provider type (e.g., 'aws', 'provider1', 'provider2')
+ extension_instance: The extension instance implementing TemplateExtension
+ """
+ if not isinstance(extension_instance, TemplateExtension):
+ raise ValueError(
+ f"Extension instance must implement TemplateExtension, got {type(extension_instance)}"
+ )
+
+ cls._extension_instances[provider_type] = extension_instance
+
+ @classmethod
+ def get_extension_class(cls, provider_type: str) -> Optional[type[BaseModel]]:
+ """Get extension configuration class for a provider.
+
+ Args:
+ provider_type: The provider type to get extension class for
+
+ Returns:
+ The extension configuration class or None if not registered
+ """
+ return cls._extensions.get(provider_type)
+
+ @classmethod
+ def get_extension_instance(cls, provider_type: str) -> Optional[TemplateExtension]:
+ """Get extension instance for a provider.
+
+ Args:
+ provider_type: The provider type to get extension instance for
+
+ Returns:
+ The extension instance or None if not registered
+ """
+ return cls._extension_instances.get(provider_type)
+
+ @classmethod
+ def has_extension(cls, provider_type: str) -> bool:
+ """Check if a provider has registered extensions.
+
+ Args:
+ provider_type: The provider type to check
+
+ Returns:
+ True if the provider has registered extensions
+ """
+ return provider_type in cls._extensions or provider_type in cls._extension_instances
+
+ @classmethod
+ def get_supported_providers(cls) -> list[str]:
+ """Get list of providers with registered extensions.
+
+ Returns:
+ List of provider types that have registered extensions
+ """
+ all_providers = set(cls._extensions.keys()) | set(cls._extension_instances.keys())
+ return list(all_providers)
+
+ @classmethod
+ def create_extension_config(
+ cls, provider_type: str, config_data: dict[str, Any]
+ ) -> Optional[BaseModel]:
+ """Create an extension configuration instance for a provider.
+
+ Args:
+ provider_type: The provider type
+ config_data: The configuration data to create the extension with
+
+ Returns:
+ The created extension configuration instance or None if provider not registered
+ """
+ extension_class = cls.get_extension_class(provider_type)
+ if extension_class:
+ return extension_class(**config_data)
+ return None
+
+ @classmethod
+ def get_extension_defaults(
+ cls, provider_type: str, config_data: Optional[dict[str, Any]] = None
+ ) -> dict[str, Any]:
+ """Get template defaults from provider extension.
+
+ Args:
+ provider_type: The provider type
+ config_data: Optional configuration data to create extension with
+
+ Returns:
+ Dictionary of template defaults from the extension
+ """
+ # Try extension instance first
+ extension_instance = cls.get_extension_instance(provider_type)
+ if extension_instance:
+ return extension_instance.to_template_defaults() # type: ignore[attr-defined]
+
+ # Try creating from extension class
+ if config_data:
+ extension_config = cls.create_extension_config(provider_type, config_data)
+ if extension_config and hasattr(extension_config, "to_template_defaults"):
+ return extension_config.to_template_defaults() # type: ignore[attr-defined]
+
+ return {}
+
+ @classmethod
+ def clear_registry(cls) -> None:
+ """Clear all registered extensions (mainly for testing)."""
+ cls._extensions.clear()
+ cls._extension_instances.clear()
+
+
+class TemplateExtensionRegistryAdapter:
+ """Instance adapter that satisfies ``TemplateExtensionRegistryPort``.
+
+ Delegates every call to the ``TemplateExtensionRegistry`` classmethods so
+ that callers in the application layer receive an ordinary injected object
+ rather than reaching for the global registry directly.
+ """
+
+ def get_extension_defaults(
+ self, provider_type: str, config_data: Optional[dict[str, Any]] = None
+ ) -> dict[str, Any]:
+ """Delegate to ``TemplateExtensionRegistry.get_extension_defaults``."""
+ return TemplateExtensionRegistry.get_extension_defaults(provider_type, config_data)
+
+
+# Convenience functions for common operations
+def register_provider_extension(provider_type: str, extension_class: type[BaseModel]) -> None:
+ """Register a provider extension."""
+ TemplateExtensionRegistry.register_extension(provider_type, extension_class)
+
+
+def get_provider_extension_defaults(
+ provider_type: str, config_data: Optional[dict[str, Any]] = None
+) -> dict[str, Any]:
+ """Get provider extension defaults."""
+ return TemplateExtensionRegistry.get_extension_defaults(provider_type, config_data)
+
+
+def has_provider_extension(provider_type: str) -> bool:
+ """Check if provider has extensions."""
+ return TemplateExtensionRegistry.has_extension(provider_type)
diff --git a/src/orb/infrastructure/scheduler/hostfactory/field_mapper.py b/src/orb/infrastructure/scheduler/hostfactory/field_mapper.py
index 21d05cc3c..1417ffc1b 100644
--- a/src/orb/infrastructure/scheduler/hostfactory/field_mapper.py
+++ b/src/orb/infrastructure/scheduler/hostfactory/field_mapper.py
@@ -9,13 +9,13 @@
class HostFactoryFieldMapper(SchedulerFieldMapper):
"""HostFactory-specific field mapping and transformations."""
- def __init__(self, provider_type: str = "aws"):
+ def __init__(self, provider_type: str | None = None):
self.provider_type = provider_type
@property
def field_mappings(self) -> Dict[str, str]:
"""Get HostFactory field mappings for the provider."""
- return HostFactoryFieldMappings.get_mappings(self.provider_type)
+ return HostFactoryFieldMappings.get_mappings(self.provider_type or "")
def map_input_fields(self, external_template: Dict[str, Any]) -> Dict[str, Any]:
"""Map HostFactory format → internal format with transformations."""
@@ -141,13 +141,26 @@ def _apply_output_transformations(self, mapped: Dict[str, Any]) -> Dict[str, Any
return {k: v for k, v in mapped.items() if v is not None and v != {} and v != []}
def _create_hf_attributes(self, instance_type: str) -> Dict[str, List[str]]:
- """Create HostFactory attributes from instance type."""
- from orb.providers.aws.utilities.ec2.instances import derive_cpu_ram_from_instance_type
+ """Create HostFactory attributes from instance type via the per-provider registry.
+
+ Delegates to the registered ``FieldMappingPort.derive_attributes`` for
+ the active provider type. Falls back to a minimal placeholder when no
+ adapter is registered (non-AWS providers that don't supply cpu/ram
+ lookup yet).
+ """
+ from orb.infrastructure.scheduler.hostfactory.field_mapping_registry import (
+ FieldMappingRegistry,
+ )
- ncpus, nram = derive_cpu_ram_from_instance_type(instance_type)
+ adapter = FieldMappingRegistry.get(self.provider_type or "")
+ if adapter is not None:
+ result = adapter.derive_attributes(instance_type)
+ if result is not None:
+ return result # type: ignore[return-value]
+ # Fallback: return a minimal attributes object so HF spec is still satisfied.
return {
"type": ["String", "X86_64"],
- "ncpus": ["Numeric", str(ncpus)],
- "nram": ["Numeric", str(nram)],
+ "ncpus": ["Numeric", "1"],
+ "nram": ["Numeric", "1024"],
}
diff --git a/src/orb/infrastructure/scheduler/hostfactory/field_mapping_port.py b/src/orb/infrastructure/scheduler/hostfactory/field_mapping_port.py
new file mode 100644
index 000000000..f8ffc32fd
--- /dev/null
+++ b/src/orb/infrastructure/scheduler/hostfactory/field_mapping_port.py
@@ -0,0 +1,46 @@
+"""Protocol defining per-provider field-mapping behaviour for the HostFactory scheduler."""
+
+from typing import Protocol, runtime_checkable
+
+
+@runtime_checkable
+class FieldMappingPort(Protocol):
+ """Per-provider HostFactory field-mapping extension point.
+
+ Implementations live under ``providers//scheduler/`` and are
+ registered via ``FieldMappingRegistry`` during provider bootstrap.
+ """
+
+ def get_mappings(self) -> dict[str, str]:
+ """Return the provider-specific HF-field → internal-field name dict.
+
+ The shared ``HostFactoryFieldMappings.MAPPINGS["generic"]`` table is
+ always applied first; this method returns only the *additional*
+ provider-specific entries that should be merged on top.
+ """
+ ... # type: ignore[return]
+
+ def apply_defaults(self, mapped: dict) -> dict:
+ """Apply provider-specific ``setdefault`` logic after field mapping.
+
+ Args:
+ mapped: The partially-mapped template dict (mutated in-place and
+ returned for convenience).
+
+ Returns:
+ The same dict with provider defaults applied.
+ """
+ ... # type: ignore[return]
+
+ def derive_attributes(self, machine_type: str | None) -> dict[str, list[str]] | None:
+ """Build the HF ``attributes`` object for a given machine / instance type.
+
+ Args:
+ machine_type: Provider-specific instance/machine type string
+ (e.g. ``"t3.medium"`` for AWS EC2).
+
+ Returns:
+ A dict suitable for the HF ``attributes`` field, or ``None`` when
+ the provider does not support cpu/ram attribute derivation.
+ """
+ ... # type: ignore[return]
diff --git a/src/orb/infrastructure/scheduler/hostfactory/field_mapping_registry.py b/src/orb/infrastructure/scheduler/hostfactory/field_mapping_registry.py
new file mode 100644
index 000000000..419c0230d
--- /dev/null
+++ b/src/orb/infrastructure/scheduler/hostfactory/field_mapping_registry.py
@@ -0,0 +1,47 @@
+"""Registry mapping provider types to FieldMappingPort instances."""
+
+from orb.infrastructure.scheduler.hostfactory.field_mapping_port import FieldMappingPort
+
+
+class FieldMappingRegistry:
+ """Simple class-variable registry mapping provider type strings to
+ ``FieldMappingPort`` implementations.
+
+ Follows the same lightweight pattern as ``CLISpecRegistry``.
+
+ Usage::
+
+ # During provider bootstrap:
+ FieldMappingRegistry.register("aws", AWSFieldMapping())
+
+ # At call site:
+ adapter = FieldMappingRegistry.get("aws")
+ if adapter is not None:
+ mapped = adapter.apply_defaults(mapped)
+ """
+
+ _adapters: dict[str, FieldMappingPort] = {}
+
+ @classmethod
+ def register(cls, provider_type: str, adapter: FieldMappingPort) -> None:
+ """Register a field-mapping adapter for *provider_type*.
+
+ Registration is idempotent — re-registering the same provider type
+ silently overwrites the previous entry.
+ """
+ cls._adapters[provider_type] = adapter
+
+ @classmethod
+ def get(cls, provider_type: str) -> FieldMappingPort | None:
+ """Return the adapter for *provider_type*, or ``None`` if not registered."""
+ return cls._adapters.get(provider_type)
+
+ @classmethod
+ def registered_providers(cls) -> list[str]:
+ """Return all registered provider type strings."""
+ return list(cls._adapters.keys())
+
+ @classmethod
+ def clear(cls) -> None:
+ """Remove all registrations (primarily for use in tests)."""
+ cls._adapters.clear()
diff --git a/src/orb/infrastructure/scheduler/hostfactory/field_mappings.py b/src/orb/infrastructure/scheduler/hostfactory/field_mappings.py
index ea8135ed3..4e82232ac 100644
--- a/src/orb/infrastructure/scheduler/hostfactory/field_mappings.py
+++ b/src/orb/infrastructure/scheduler/hostfactory/field_mappings.py
@@ -6,7 +6,15 @@
class HostFactoryFieldMappings:
"""Registry of HostFactory-specific field mappings per provider."""
- # Field mappings organized by provider
+ # Field mappings organized by provider.
+ #
+ # NOTE: AWS entries here are kept as a no-bootstrap fallback so static
+ # consumers (e.g. unit tests that exercise the mapper without booting the
+ # AWS provider) keep working. The canonical AWS mapping lives in
+ # ``providers/aws/scheduler/hostfactory_field_mapping.py:AWSFieldMapping``
+ # registered into ``FieldMappingRegistry`` during provider bootstrap.
+ # Adding a new provider should NOT add entries here — register a
+ # ``FieldMappingPort`` adapter instead.
MAPPINGS = {
# Generic fields (work with any provider)
"generic": {
@@ -95,23 +103,6 @@ def get_supported_providers(cls) -> list[str]:
"""Get list of supported providers for HostFactory."""
return [key for key in cls.MAPPINGS.keys() if key != "generic"]
- @classmethod
- def apply_aws_defaults(cls, mapped: dict) -> dict:
- """
- Apply AWS-specific setdefault logic that depends on launch_template_id.
-
- When launch_template_id is set the LT already encodes network config,
- so subnet_ids and security_group_ids should not be defaulted to empty lists.
- """
- mapped.setdefault("max_instances", 1)
- mapped.setdefault("price_type", "ondemand")
- mapped.setdefault("allocation_strategy", "lowestPrice")
- if not mapped.get("launch_template_id"):
- mapped.setdefault("subnet_ids", [])
- mapped.setdefault("security_group_ids", [])
- mapped.setdefault("tags", {})
- return mapped
-
@classmethod
def is_provider_specific_field(cls, provider_type: str, field_name: str) -> bool:
"""
diff --git a/src/orb/infrastructure/scheduler/hostfactory/hostfactory_strategy.py b/src/orb/infrastructure/scheduler/hostfactory/hostfactory_strategy.py
index 6db3f6d92..257d5b52d 100644
--- a/src/orb/infrastructure/scheduler/hostfactory/hostfactory_strategy.py
+++ b/src/orb/infrastructure/scheduler/hostfactory/hostfactory_strategy.py
@@ -12,7 +12,6 @@
from orb.application.request.dto import RequestDTO
from orb.infrastructure.scheduler.base.strategy import BaseSchedulerStrategy
from orb.infrastructure.scheduler.hostfactory.field_mapper import HostFactoryFieldMapper
-from orb.infrastructure.scheduler.hostfactory.field_mappings import HostFactoryFieldMappings
from orb.infrastructure.scheduler.hostfactory.transformations import HostFactoryTransformations
from orb.infrastructure.template.dtos import TemplateDTO
from orb.infrastructure.utilities.common.string_utils import extract_provider_type
@@ -140,7 +139,15 @@ def _map_template_fields(
if "template_id" in mapped:
mapped["name"] = template.get("name", mapped["template_id"])
- HostFactoryFieldMappings.apply_aws_defaults(mapped)
+ # Apply per-provider field defaults via the registry.
+ provider_type = self._get_active_provider_type()
+ from orb.infrastructure.scheduler.hostfactory.field_mapping_registry import (
+ FieldMappingRegistry,
+ )
+
+ adapter = FieldMappingRegistry.get(provider_type)
+ if adapter is not None:
+ adapter.apply_defaults(mapped)
mapped["created_at"] = template.get("created_at")
mapped["updated_at"] = template.get("updated_at")
@@ -592,14 +599,28 @@ def format_return_requests_response(self, requests: list[Any]) -> dict[str, Any]
}
def _build_hf_attributes(self, instance_type: str) -> dict[str, list[str]]:
- """Build IBM HF attributes dict from an instance type string."""
- from orb.providers.aws.utilities.ec2.instances import derive_cpu_ram_from_instance_type
+ """Build IBM HF attributes dict from an instance type string.
- ncpus, nram = derive_cpu_ram_from_instance_type(instance_type)
+ Delegates to the per-provider ``FieldMappingPort.derive_attributes``
+ via the registry. Falls back to a minimal placeholder so callers
+ always receive a valid attributes object.
+ """
+ from orb.infrastructure.scheduler.hostfactory.field_mapping_registry import (
+ FieldMappingRegistry,
+ )
+
+ provider_type = self._get_active_provider_type()
+ adapter = FieldMappingRegistry.get(provider_type)
+ if adapter is not None:
+ result = adapter.derive_attributes(instance_type)
+ if result is not None:
+ return result
+
+ # Fallback: return a minimal attributes object so HF spec is still satisfied.
return {
"type": ["String", "X86_64"],
- "ncpus": ["Numeric", str(ncpus)],
- "nram": ["Numeric", str(nram)],
+ "ncpus": ["Numeric", "1"],
+ "nram": ["Numeric", "1024"],
}
def format_templates_for_dispatch(self, templates: list[dict]) -> list[dict]:
@@ -654,6 +675,9 @@ def format_request_status_response(self, requests: list[RequestDTO]) -> dict[str
),
"message": req_dict.get("message", ""),
"machines": machines,
+ # ORB extension: expose resource_ids so callers can discover the backing
+ # fleet / ASG identifier without a separate EC2 tag lookup.
+ "resource_ids": req_dict.get("resource_ids") or [],
}
# Add provider information if present
@@ -664,6 +688,20 @@ def format_request_status_response(self, requests: list[RequestDTO]) -> dict[str
if req_dict.get("provider_api"):
hf_request["providerApi"] = req_dict["provider_api"]
+ # Surface ProviderFulfilment capacity fields so callers can verify
+ # weighted-fleet fulfilment (target capacity units vs delivered).
+ # IBM Symphony spec is silent on these fields; expose as ORB
+ # extension keys when the provider has populated them.
+ for cap_field in (
+ "target_units",
+ "fulfilled_units",
+ "running_count",
+ "pending_count",
+ ):
+ value = req_dict.get(cap_field)
+ if value is not None:
+ hf_request[cap_field] = value
+
formatted_requests.append(hf_request)
return {"requests": formatted_requests}
diff --git a/src/orb/infrastructure/storage/registration.py b/src/orb/infrastructure/storage/registration.py
index 63ca3bfc4..e91f7f83b 100644
--- a/src/orb/infrastructure/storage/registration.py
+++ b/src/orb/infrastructure/storage/registration.py
@@ -22,13 +22,16 @@ def register_all_storage_types() -> None:
register_sql_storage()
- from orb.providers.aws.storage.registration import (
- register_aurora_storage,
- register_dynamodb_storage,
- )
-
- register_dynamodb_storage()
- register_aurora_storage()
+ try:
+ from orb.providers.aws.storage.registration import (
+ register_aurora_storage,
+ register_dynamodb_storage,
+ )
+
+ register_dynamodb_storage()
+ register_aurora_storage()
+ except ImportError:
+ pass # [aws] extra not installed; DynamoDB and Aurora storage unavailable
def get_available_storage_types() -> list:
diff --git a/src/orb/infrastructure/template/dtos.py b/src/orb/infrastructure/template/dtos.py
index 2fd2ca335..52bf122f8 100644
--- a/src/orb/infrastructure/template/dtos.py
+++ b/src/orb/infrastructure/template/dtos.py
@@ -4,9 +4,10 @@
from datetime import datetime
from typing import Any, Optional
-from pydantic import Field, model_validator
+from pydantic import BaseModel, Field, field_serializer, model_validator
from orb.application.dto.base import BaseDTO
+from orb.infrastructure.registry.template_extension_registry import TemplateExtensionRegistry
class TemplateDTO(BaseDTO):
@@ -56,7 +57,6 @@ class TemplateDTO(BaseDTO):
key_name: Optional[str] = None
user_data: Optional[str] = None
instance_profile: Optional[str] = None
- launch_template_id: Optional[str] = None
# Advanced configuration
monitoring_enabled: Optional[bool] = None
@@ -65,6 +65,9 @@ class TemplateDTO(BaseDTO):
tags: dict[str, Any] = Field(default_factory=dict)
metadata: dict[str, Any] = Field(default_factory=dict)
+ # Typed provider-specific configuration, populated via TemplateExtensionRegistry
+ provider_config: Optional[BaseModel] = None
+
# Provider-specific data (keyed by provider name, e.g. {"aws": {...}})
provider_data: dict[str, Any] = Field(default_factory=dict)
@@ -83,6 +86,13 @@ class TemplateDTO(BaseDTO):
# Legacy fields
version: Optional[str] = None
+ @field_serializer("provider_config")
+ def _serialize_provider_config(self, value: Optional[BaseModel]) -> Optional[dict[str, Any]]:
+ """Serialise the typed provider_config to a plain dict for model_dump() consumers."""
+ if value is None:
+ return None
+ return value.model_dump()
+
@model_validator(mode="before")
@classmethod
def _set_defaults(cls, data: Any) -> Any:
@@ -95,28 +105,21 @@ def _set_defaults(cls, data: Any) -> Any:
@classmethod
def from_domain(cls, template) -> "TemplateDTO":
"""Convert domain template to DTO."""
- # Pack AWS-specific fields into metadata.
- _fleet_type = getattr(template, "fleet_type", None)
- _fleet_type_str: Optional[str] = (
- str(_fleet_type.value)
- if _fleet_type is not None and hasattr(_fleet_type, "value")
- else (_fleet_type if _fleet_type is None else str(_fleet_type))
- )
- _abis = getattr(template, "abis_instance_requirements", None)
- _fleet_role = getattr(template, "fleet_role", None)
- _percent_on_demand = getattr(template, "percent_on_demand", None)
-
- aws_extras: dict[str, Any] = {}
- if _fleet_type_str is not None:
- aws_extras["fleet_type"] = _fleet_type_str
- if _fleet_role is not None:
- aws_extras["fleet_role"] = _fleet_role
- if _percent_on_demand is not None:
- aws_extras["percent_on_demand"] = _percent_on_demand
- if _abis is not None:
- aws_extras["abis_instance_requirements"] = _abis.to_aws_dict()
-
- metadata = {**getattr(template, "metadata", {}), **aws_extras}
+ # Delegate provider-specific field extraction to the extension registry.
+ # Each provider registers an extension class that knows which fields to
+ # capture. Infrastructure layer stays provider-agnostic.
+ _provider_type = getattr(template, "provider_type", None)
+ _provider_config: Optional[BaseModel] = None
+ if _provider_type:
+ # Serialise the domain object to a plain dict so the extension class
+ # can pick up its own fields (with extra="ignore").
+ if hasattr(template, "model_dump"):
+ _template_data = template.model_dump()
+ else:
+ _template_data = vars(template)
+ _provider_config = TemplateExtensionRegistry.create_extension_config(
+ _provider_type, _template_data
+ )
return cls(
# Core fields
@@ -151,15 +154,16 @@ def from_domain(cls, template) -> "TemplateDTO":
key_name=getattr(template, "key_name", None),
user_data=getattr(template, "user_data", None),
instance_profile=getattr(template, "instance_profile", None),
- launch_template_id=getattr(template, "launch_template_id", None),
# Advanced configuration
monitoring_enabled=getattr(template, "monitoring_enabled", None),
- # Tags and metadata
+ # Tags and metadata (cross-provider opaque data only)
tags=getattr(template, "tags", {}),
- metadata=metadata,
+ metadata=getattr(template, "metadata", {}),
+ # Typed provider-specific configuration (populated via registry)
+ provider_config=_provider_config,
provider_data=getattr(template, "provider_data", {}),
- # Provider configuration
- provider_type=getattr(template, "provider_type", None),
+ # Provider identification
+ provider_type=_provider_type,
provider_name=getattr(template, "provider_name", None),
provider_api=getattr(template, "provider_api", None),
# Timestamps
diff --git a/src/orb/interface/infrastructure_command_handler.py b/src/orb/interface/infrastructure_command_handler.py
index 60ff7bbba..46f460771 100644
--- a/src/orb/interface/infrastructure_command_handler.py
+++ b/src/orb/interface/infrastructure_command_handler.py
@@ -7,6 +7,7 @@
from orb.domain.base.ports.console_port import ConsolePort
from orb.infrastructure.di.container import get_container
from orb.infrastructure.error.decorators import handle_interface_exceptions
+from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
@handle_interface_exceptions(context="infrastructure_discover", interface_type="cli")
@@ -113,8 +114,15 @@ def _show_provider_infrastructure(provider: Dict[str, Any]) -> None:
config = provider.get("config", {})
if config:
- console.info(f"Region: {config.get('region', 'N/A')}")
- console.info(f"Profile: {config.get('profile', 'N/A')}")
+ provider_type = provider.get("type", "")
+ spec = CLISpecRegistry.get(provider_type)
+ if spec is not None:
+ for label, value in spec.format_display(config):
+ console.info(f"{label}: {value}")
+ else:
+ for key, value in config.items():
+ label = key.replace("_", " ").title()
+ console.info(f"{label}: {value}")
template_defaults = provider.get("template_defaults", {})
if template_defaults:
@@ -174,7 +182,11 @@ def _get_active_providers() -> List[Dict[str, Any]]:
registry_service = get_container().get(ProviderRegistryService)
registered_types = registry_service.get_registered_provider_types()
- default_type = registered_types[0] if registered_types else "aws"
+ if not registered_types:
+ raise RuntimeError(
+ "No providers are registered. Run 'orb init' to configure a provider."
+ )
+ default_type = registered_types[0]
return [
{
"name": "default",
@@ -199,7 +211,11 @@ def _get_active_providers() -> List[Dict[str, Any]]:
registry_service = get_container().get(ProviderRegistryService)
registered_types = registry_service.get_registered_provider_types()
- default_type = registered_types[0] if registered_types else "aws"
+ if not registered_types:
+ raise RuntimeError(
+ "No providers are registered. Run 'orb init' to configure a provider."
+ )
+ default_type = registered_types[0]
active_providers = [
{
"name": "default",
diff --git a/src/orb/interface/init_command_handler.py b/src/orb/interface/init_command_handler.py
index 632aaf428..4bbfe2f68 100644
--- a/src/orb/interface/init_command_handler.py
+++ b/src/orb/interface/init_command_handler.py
@@ -16,6 +16,7 @@
from orb.domain.base.ports.provider_registry_port import ProviderRegistryPort
from orb.infrastructure.di.container import get_container
from orb.infrastructure.logging.logger import get_logger
+from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
logger = get_logger(__name__)
@@ -602,10 +603,22 @@ def _get_default_config(args) -> Dict[str, Any]:
strategy.get_cli_infrastructure_defaults(args) if strategy is not None else {}
)
+ # Use the registered CLI spec to extract provider config when available.
+ # Fall back to the init-level --profile / --region flags for backward
+ # compatibility with callers that have not yet adopted provider-specific flags.
+ spec = CLISpecRegistry.get(provider_type)
+ if spec is not None:
+ spec_config = spec.extract_config(args)
+ profile = spec_config.get("profile") or getattr(args, "profile", None) or None
+ region = spec_config.get("region") or getattr(args, "region", None) or default_region
+ else:
+ profile = getattr(args, "profile", None) or None
+ region = getattr(args, "region", None) or default_region
+
first_provider = {
"type": provider_type,
- "profile": args.profile or None,
- "region": args.region or default_region,
+ "profile": profile,
+ "region": region,
"infrastructure_defaults": infrastructure_defaults,
}
diff --git a/src/orb/interface/mcp/server/handler.py b/src/orb/interface/mcp/server/handler.py
index b0e5c06d4..8630e1f7e 100644
--- a/src/orb/interface/mcp/server/handler.py
+++ b/src/orb/interface/mcp/server/handler.py
@@ -4,6 +4,7 @@
import sys
from typing import Any
+from orb.bootstrap import Application
from orb.infrastructure.di.container import get_container
from orb.infrastructure.error.decorators import handle_interface_exceptions
from orb.infrastructure.logging.logger import get_logger
@@ -29,11 +30,16 @@ async def handle_mcp_serve(args) -> dict[str, Any]:
host = getattr(args, "host", "localhost")
stdio_mode = getattr(args, "stdio", False)
- # Get application instance from DI container
- container = get_container()
+ # Bootstrap the application so that the DI container has all providers,
+ # CQRS handlers, and configuration registered. Without this the MCP tool
+ # handlers operate against a bare container and every request fails with
+ # "No strategy found for provider".
+ app = Application()
+ if not await app.initialize():
+ raise RuntimeError("Failed to initialize ORB application for MCP server")
- # Create MCP server instance
- mcp_server = OpenResourceBrokerMCPServer(app=container)
+ # Create MCP server instance with the initialized DI container
+ mcp_server = OpenResourceBrokerMCPServer(app=get_container())
if stdio_mode:
# Run in stdio mode for direct MCP client communication
diff --git a/src/orb/interface/provider_config_handler.py b/src/orb/interface/provider_config_handler.py
index 25b6e3206..09f0e8c96 100644
--- a/src/orb/interface/provider_config_handler.py
+++ b/src/orb/interface/provider_config_handler.py
@@ -5,10 +5,10 @@
from orb.application.dto.interface_response import InterfaceResponse
from orb.config.platform_dirs import get_config_location
-from orb.domain.base.ports.provider_cli_spec_port import CLISpecRegistry
from orb.infrastructure.di.container import get_container
from orb.infrastructure.error.decorators import handle_interface_exceptions
from orb.infrastructure.logging.logger import get_logger
+from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
from orb.interface.response_formatting_service import ResponseFormattingService
logger = get_logger(__name__)
@@ -29,7 +29,13 @@ async def handle_provider_add(args) -> dict[str, Any]:
with open(config_file) as f:
config = json.load(f)
- provider_type = getattr(args, "provider_type", "aws")
+ provider_type = getattr(args, "provider_type", None)
+ if not provider_type:
+ return {
+ "error": True,
+ "message": "Provider type is required. Specify --provider-type.",
+ "exit_code": 1,
+ }
spec = CLISpecRegistry.get(provider_type)
if spec is None:
@@ -152,27 +158,21 @@ async def handle_provider_update(args) -> dict[str, Any]:
}
# Infer provider type from stored record
- provider_type = provider.get("type", "aws")
+ provider_type = provider.get("type") or ""
spec = CLISpecRegistry.get(provider_type)
provider_config = provider.get("config", {})
- if spec is not None:
- partial = spec.extract_partial_config(args)
- if not partial:
- return {"error": True, "message": "No updates specified.", "exit_code": 1}
- provider_config.update(partial)
- else:
- # Fallback: apply any non-None aws_* attrs directly
- updated = False
- if getattr(args, "aws_region", None):
- provider_config["region"] = args.aws_region
- updated = True
- if getattr(args, "aws_profile", None):
- provider_config["profile"] = args.aws_profile
- updated = True
- if not updated:
- return {"error": True, "message": "No updates specified.", "exit_code": 1}
+ if spec is None:
+ return {
+ "error": True,
+ "message": f"Unknown provider type '{provider_type}'. No CLI spec registered.",
+ "exit_code": 1,
+ }
+ partial = spec.extract_partial_config(args)
+ if not partial:
+ return {"error": True, "message": "No updates specified.", "exit_code": 1}
+ provider_config.update(partial)
# Test updated credentials
success, error = _test_provider_credentials(provider_type, provider_config)
diff --git a/src/orb/providers/README.md b/src/orb/providers/README.md
index c392c0ac6..a6dd81b8c 100644
--- a/src/orb/providers/README.md
+++ b/src/orb/providers/README.md
@@ -11,7 +11,7 @@ The provider layer contains cloud-specific implementations that extend the core
When accessing provider extension configurations, always use the `TemplateExtensionRegistry`:
```python
-from src.domain.template.extensions import TemplateExtensionRegistry
+from orb.infrastructure.registry.template_extension_registry import TemplateExtensionRegistry
# Get extension config with schema defaults
extension_config = TemplateExtensionRegistry.create_extension_config(
@@ -518,6 +518,12 @@ class NewCloudProvider(ProviderInterface):
- Provide clear extension examples
- Support community contributions
+## Developer Guide
+
+For a step-by-step walkthrough of adding a new provider — including the mandatory glue points, all extension point registries, the `OperationOutcome` contract, anti-patterns to avoid, and the recommended test layout — see:
+
+- [Adding a Provider](../../docs/root/developer_guide/adding_a_provider.md)
+
---
This provider layer enables seamless multi-cloud support while maintaining the clean architecture and cloud-agnostic design of the core system. Built on open standards and designed for extensibility, it provides a solid foundation for supporting any cloud platform.
diff --git a/src/orb/providers/aws/auth/cognito_strategy.py b/src/orb/providers/aws/auth/cognito_strategy.py
index 49e59e67b..01c7aa345 100644
--- a/src/orb/providers/aws/auth/cognito_strategy.py
+++ b/src/orb/providers/aws/auth/cognito_strategy.py
@@ -1,11 +1,18 @@
"""AWS Cognito authentication strategy."""
-from typing import Optional
+from __future__ import annotations
+
+import asyncio
+import time
+from base64 import urlsafe_b64decode
+from typing import TYPE_CHECKING, Any, Optional
import boto3
import jwt
+import requests
from botocore.config import Config
from botocore.exceptions import ClientError
+from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
_DEFAULT_CONFIG = Config(
connect_timeout=10,
@@ -22,11 +29,19 @@
AuthStatus,
)
+if TYPE_CHECKING:
+ pass
+
@injectable
class CognitoAuthStrategy(AuthPort):
"""Authentication strategy using AWS Cognito User Pools."""
+ # Class-level JWKS cache: maps jwks_url → {"fetched_at": float, "keys": list[dict]}
+ # Shared across all instances so pool restarts benefit from warm entries.
+ _jwks_cache: dict[str, dict[str, Any]] = {}
+ _cache_ttl_seconds: int = 3600
+
def __init__(
self,
logger: LoggingPort,
@@ -228,6 +243,54 @@ async def revoke_token(self, token: str) -> bool:
self._logger.error("Token revocation error: %s", e)
return False
+ @classmethod
+ def from_auth_config(cls, auth_config: Any) -> CognitoAuthStrategy:
+ """
+ Build strategy instance from AuthConfig.
+
+ Extracts the provider_auth.cognito sub-config and constructs a
+ CognitoAuthStrategy. A LoggingPort is obtained from the DI container
+ when available; otherwise a plain logging adapter is used.
+
+ Args:
+ auth_config: AuthConfig instance with optional provider_auth.cognito sub-config
+
+ Returns:
+ Configured CognitoAuthStrategy
+ """
+ from orb.infrastructure.adapters.logging_adapter import LoggingAdapter
+
+ provider_auth = getattr(auth_config, "provider_auth", None)
+ cognito_cfg = getattr(provider_auth, "cognito", None) if provider_auth is not None else None
+
+ user_pool_id: str = (
+ getattr(cognito_cfg, "user_pool_id", "") if cognito_cfg is not None else ""
+ )
+ client_id: str = getattr(cognito_cfg, "client_id", "") if cognito_cfg is not None else ""
+ region: str = (
+ getattr(cognito_cfg, "region", "us-east-1") if cognito_cfg is not None else "us-east-1"
+ )
+ jwks_url: Optional[str] = (
+ getattr(cognito_cfg, "jwks_url", None) if cognito_cfg is not None else None
+ )
+
+ try:
+ from orb.domain.base.ports import LoggingPort
+ from orb.infrastructure.di.container import get_container
+
+ logger: LoggingPort = get_container().get(LoggingPort)
+ except Exception:
+ logger = LoggingAdapter() # type: ignore[assignment]
+
+ return cls(
+ logger=logger,
+ user_pool_id=user_pool_id,
+ client_id=client_id,
+ region=region,
+ jwks_url=jwks_url,
+ enabled=True,
+ )
+
def get_strategy_name(self) -> str:
"""
Get strategy name.
@@ -246,37 +309,80 @@ def is_enabled(self) -> bool:
"""
return self.enabled
- async def _get_public_key(self, kid: str) -> Optional[str]:
+ @staticmethod
+ def _b64url_to_int(val: str) -> int:
+ """Decode a base64url-encoded string to an integer (used for RSA key fields)."""
+ padded = val + "=" * (-len(val) % 4)
+ return int.from_bytes(urlsafe_b64decode(padded), "big")
+
+ async def _get_public_key(self, kid: str) -> Any:
"""
Get public key from Cognito JWKS endpoint.
+ Fetches the JWKS (using the class-level cache; re-fetches after TTL),
+ locates the entry matching ``kid``, and converts it to a ``cryptography``
+ RSAPublicKey object suitable for use with PyJWT.
+
+ The synchronous ``requests.get`` call is offloaded via
+ ``asyncio.to_thread`` so the event loop is not blocked during the fetch.
+
+ On ``jwt.InvalidTokenError`` (non-RSA key) the cache entry is evicted so
+ that a key rotation does not lock out valid tokens indefinitely.
+
Args:
kid: Key ID from token header
Returns:
- Public key for token verification
+ RSAPublicKey for token verification, or None if not found
+
+ Raises:
+ jwt.InvalidTokenError: If the matched key is not an RSA key
"""
try:
- # In production, you would cache JWKS and implement appropriate key rotation
- # This is a simplified implementation
- import requests
-
- # Add timeout to prevent hanging connections (security best practice)
- response = requests.get(self.jwks_url, timeout=30)
- jwks = response.json()
+ jwks = await self._fetch_jwks_cached()
for key in jwks.get("keys", []):
if key.get("kid") == kid:
- # Convert JWK to PEM format (simplified)
- # In production, use a appropriate JWK library
- return key # Return the key dict for now
+ if key.get("kty") != "RSA":
+ # Evict the cache entry so a subsequent rotation is picked up.
+ self._jwks_cache.pop(self.jwks_url, None)
+ raise jwt.InvalidTokenError(
+ f"Unsupported key type: {key.get('kty')!r}. Only RSA keys are supported."
+ )
+ n = self._b64url_to_int(key["n"])
+ e = self._b64url_to_int(key["e"])
+ return RSAPublicNumbers(e=e, n=n).public_key()
return None
+ except jwt.InvalidTokenError:
+ raise
except Exception as e:
self._logger.error("Failed to get public key: %s", e)
return None
+ async def _fetch_jwks_cached(self) -> dict[str, Any]:
+ """Return the JWKS document for this strategy's endpoint.
+
+ Serves from the class-level cache when the entry is younger than
+ ``_cache_ttl_seconds``; otherwise fetches fresh via
+ ``asyncio.to_thread`` and repopulates the cache.
+ """
+ cached = self._jwks_cache.get(self.jwks_url)
+ if cached is not None:
+ age = time.monotonic() - cached["fetched_at"]
+ if age < self._cache_ttl_seconds:
+ return cached["jwks"] # type: ignore[return-value]
+
+ def _do_fetch() -> dict[str, Any]:
+ response = requests.get(self.jwks_url, timeout=30)
+ response.raise_for_status()
+ return response.json() # type: ignore[no-any-return]
+
+ jwks: dict[str, Any] = await asyncio.to_thread(_do_fetch)
+ self._jwks_cache[self.jwks_url] = {"fetched_at": time.monotonic(), "jwks": jwks}
+ return jwks
+
def _map_groups_to_roles(self, groups: list[str]) -> list[str]:
"""
Map Cognito groups to application roles.
diff --git a/src/orb/providers/aws/auth/iam_strategy.py b/src/orb/providers/aws/auth/iam_strategy.py
index cce79918e..d3bdc80ab 100644
--- a/src/orb/providers/aws/auth/iam_strategy.py
+++ b/src/orb/providers/aws/auth/iam_strategy.py
@@ -1,6 +1,9 @@
"""AWS IAM authentication strategy."""
-from typing import Any, Optional
+from __future__ import annotations
+
+import os
+from typing import TYPE_CHECKING, Any, Optional
from botocore.config import Config
from botocore.exceptions import ClientError, NoCredentialsError
@@ -15,6 +18,9 @@
)
from orb.providers.aws.session_factory import AWSSessionFactory
+if TYPE_CHECKING:
+ pass
+
_DEFAULT_CONFIG = Config(
connect_timeout=10,
read_timeout=30,
@@ -28,8 +34,6 @@ class IAMAuthStrategy(AuthPort):
_DEFAULT_ADMIN_ROLE_PATTERNS: frozenset[str] = frozenset({"Admin", "Administrator", "OrbAdmin"})
- ASSUME_ALL_PERMISSIONS: bool = False
-
def __init__(
self,
logger: LoggingPort,
@@ -62,7 +66,32 @@ def __init__(
"ec2:TerminateInstances",
]
self.enabled = enabled
- self._assume_permissions = assume_permissions
+
+ # assume_permissions is a dev-only escape hatch. It is only honoured when
+ # the operator has explicitly set ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY=true
+ # in the environment. Without that env-var the flag is silently disabled
+ # so that a misconfigured production deployment cannot accidentally bypass
+ # real IAM evaluation.
+ _dev_env_var = os.environ.get("ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY", "").lower()
+ _dev_override_active = _dev_env_var == "true"
+
+ if assume_permissions and not _dev_override_active:
+ self._logger.critical(
+ "IAM assume_permissions=True is set in config but "
+ "ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY env var is not 'true'. "
+ "Treating as assume_permissions=False to prevent privilege bypass in production. "
+ "Set ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY=true only in non-production environments."
+ )
+ self._assume_permissions = False
+ else:
+ self._assume_permissions = assume_permissions
+
+ if self._assume_permissions:
+ self._logger.critical(
+ "IAM assume_permissions=True is ACTIVE (ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY=true). "
+ "All required_actions are granted without AWS evaluation. "
+ "This MUST NOT be used in production."
+ )
if not self._assume_permissions:
self._logger.error(
@@ -184,6 +213,54 @@ async def revoke_token(self, token: str) -> bool:
"""
return True
+ @classmethod
+ def from_auth_config(cls, auth_config: Any) -> IAMAuthStrategy:
+ """
+ Build strategy instance from AuthConfig.
+
+ Extracts the provider_auth.iam sub-config and constructs an IAMAuthStrategy.
+ A LoggingPort is obtained from the DI container when available; otherwise a
+ plain logging adapter is used so the classmethod stays self-contained.
+
+ Args:
+ auth_config: AuthConfig instance with optional provider_auth.iam sub-config
+
+ Returns:
+ Configured IAMAuthStrategy
+ """
+ from orb.infrastructure.adapters.logging_adapter import LoggingAdapter
+
+ provider_auth = getattr(auth_config, "provider_auth", None)
+ iam_cfg = getattr(provider_auth, "iam", None) if provider_auth is not None else None
+
+ region: str = (
+ getattr(iam_cfg, "region", "us-east-1") if iam_cfg is not None else "us-east-1"
+ )
+ profile: Optional[str] = getattr(iam_cfg, "profile", None) if iam_cfg is not None else None
+ required_actions: list[str] = (
+ getattr(iam_cfg, "required_actions", []) if iam_cfg is not None else []
+ )
+ assume_permissions: bool = (
+ getattr(iam_cfg, "assume_permissions", False) if iam_cfg is not None else False
+ )
+
+ try:
+ from orb.domain.base.ports import LoggingPort
+ from orb.infrastructure.di.container import get_container
+
+ logger: LoggingPort = get_container().get(LoggingPort)
+ except Exception:
+ logger = LoggingAdapter() # type: ignore[assignment]
+
+ return cls(
+ logger=logger,
+ region=region,
+ profile=profile,
+ required_actions=required_actions,
+ enabled=True,
+ assume_permissions=assume_permissions,
+ )
+
def get_strategy_name(self) -> str:
"""
Get strategy name.
diff --git a/src/orb/providers/aws/defaults_loader.py b/src/orb/providers/aws/defaults_loader.py
new file mode 100644
index 000000000..420f865fa
--- /dev/null
+++ b/src/orb/providers/aws/defaults_loader.py
@@ -0,0 +1,37 @@
+"""AWS provider defaults loader."""
+
+from __future__ import annotations
+
+import json
+
+from orb.domain.base.ports.provider_defaults_loader_port import ProviderDefaultsLoaderPort
+
+
+class AWSDefaultsLoader:
+ """Loads defaults from the bundled ``aws_defaults.json`` config file.
+
+ Satisfies :class:`~orb.domain.base.ports.provider_defaults_loader_port.ProviderDefaultsLoaderPort`.
+ """
+
+ def load_defaults(self) -> dict:
+ """Return AWS provider defaults from the bundled ``aws_defaults.json``.
+
+ Returns:
+ Raw configuration dictionary contributed by the AWS provider.
+ Returns an empty dict if the file cannot be read.
+ """
+ try:
+ from importlib.resources import files
+
+ text = (
+ files("orb.providers.aws.config")
+ .joinpath("aws_defaults.json")
+ .read_text(encoding="utf-8")
+ )
+ return json.loads(text)
+ except Exception:
+ return {}
+
+
+# Runtime check that AWSDefaultsLoader satisfies the protocol
+assert isinstance(AWSDefaultsLoader(), ProviderDefaultsLoaderPort)
diff --git a/src/orb/providers/aws/domain/template/aws_template_aggregate.py b/src/orb/providers/aws/domain/template/aws_template_aggregate.py
index c10996414..f628d9e2f 100644
--- a/src/orb/providers/aws/domain/template/aws_template_aggregate.py
+++ b/src/orb/providers/aws/domain/template/aws_template_aggregate.py
@@ -14,7 +14,6 @@
from orb.domain.template.template_aggregate import Template
from orb.providers.aws.domain.template.value_objects import (
- AWSAllocationStrategy,
AWSConfiguration,
AWSFleetType,
AWSInstanceType,
@@ -22,8 +21,8 @@
AWSSubnetId,
AWSTags,
ProviderApi,
- normalise_allocation_strategy,
)
+from orb.providers.aws.value_objects import AWSAllocationStrategy, normalise_allocation_strategy
class AWSOptionalIntegerRange(BaseModel):
@@ -203,6 +202,11 @@ class AWSTemplate(Template):
model_config = ConfigDict(arbitrary_types_allowed=True)
+ # Typed provider-config forwarded from TemplateDTO (round-trip via model_dump).
+ # Accepts a dict (serialised AWSTemplateDTOConfig) or None; values are promoted
+ # to their respective fields in validate_aws_template before use.
+ provider_config: Optional[dict[str, Any]] = None
+
# AWS-specific fields
provider_api: Optional[ProviderApi] = None # type: ignore[assignment]
fleet_type: Optional[AWSFleetType] = None
@@ -253,6 +257,38 @@ def validate_aws_template(self) -> "AWSTemplate":
# (generic/example templates may have empty subnet_ids/image_id, filled at runtime
# from provider.template_defaults via _coalesce_merge)
+ # Promote AWS-specific fields from provider_config dict (DTO round-trip path).
+ # provider_config is injected by TemplateDTO.from_domain via AWSTemplateDTOConfig.
+ _pc = getattr(self, "provider_config", None)
+ if isinstance(_pc, dict):
+ if not self.fleet_type:
+ _ft = _pc.get("fleet_type")
+ if _ft:
+ try:
+ object.__setattr__(self, "fleet_type", AWSFleetType(str(_ft).lower()))
+ except (ValueError, TypeError):
+ pass
+ if not self.fleet_role:
+ _fr = _pc.get("fleet_role")
+ if _fr:
+ object.__setattr__(self, "fleet_role", _fr)
+ if self.percent_on_demand is None:
+ _pod = _pc.get("percent_on_demand")
+ if _pod is not None:
+ object.__setattr__(self, "percent_on_demand", int(_pod))
+ if not self.launch_template_id:
+ _ltid = _pc.get("launch_template_id")
+ if _ltid:
+ object.__setattr__(self, "launch_template_id", _ltid)
+ if self.abis_instance_requirements is None:
+ _abis = _pc.get("abis_instance_requirements")
+ if _abis is not None:
+ object.__setattr__(
+ self,
+ "abis_instance_requirements",
+ ABISInstanceRequirements.model_validate(_abis),
+ )
+
# Auto-assign default fleet_type if not provided
# Set fleet_type from metadata if not already set
if not self.fleet_type:
diff --git a/src/orb/providers/aws/domain/template/aws_template_dto_config.py b/src/orb/providers/aws/domain/template/aws_template_dto_config.py
new file mode 100644
index 000000000..410942b66
--- /dev/null
+++ b/src/orb/providers/aws/domain/template/aws_template_dto_config.py
@@ -0,0 +1,39 @@
+"""AWS-specific typed DTO configuration for TemplateDTO serialisation."""
+
+from typing import Any, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class AWSTemplateDTOConfig(BaseModel):
+ model_config = ConfigDict(extra="ignore")
+ """Typed container for AWS-specific fields on TemplateDTO.
+
+ This class holds only the fields that are AWS-specific and were
+ previously scattered between the top-level TemplateDTO attributes
+ and the opaque ``metadata`` dict. It is registered with
+ ``TemplateExtensionRegistry`` so that ``TemplateDTO.from_domain``
+ can delegate construction to the registry rather than containing
+ AWS knowledge directly.
+ """
+
+ # EC2 Fleet / Spot Fleet configuration
+ fleet_type: Optional[str] = Field(None, description="Fleet type (maintain, request, instant)")
+ fleet_role: Optional[str] = Field(None, description="IAM role ARN for Spot Fleet")
+ percent_on_demand: Optional[int] = Field(
+ None, description="Percentage of On-Demand capacity in a heterogeneous fleet"
+ )
+
+ # Launch template reference
+ launch_template_id: Optional[str] = Field(
+ None, description="EC2 Launch Template ID to use instead of inline spec"
+ )
+
+ # Attribute-based instance selection payload (already serialised to dict)
+ abis_instance_requirements: Optional[dict[str, Any]] = Field(
+ None, description="InstanceRequirements dict for attribute-based instance selection"
+ )
+
+ def to_template_defaults(self) -> dict[str, Any]:
+ """Return a flat dict of non-None values suitable for template defaults merging."""
+ return {k: v for k, v in self.model_dump().items() if v is not None}
diff --git a/src/orb/providers/aws/exceptions/aws_exceptions.py b/src/orb/providers/aws/exceptions/aws_exceptions.py
index 3adceed9e..6486bf3be 100644
--- a/src/orb/providers/aws/exceptions/aws_exceptions.py
+++ b/src/orb/providers/aws/exceptions/aws_exceptions.py
@@ -16,22 +16,42 @@ def __init__(
message: str,
details: Optional[dict[str, Any]] = None,
error_code: Optional[str] = None,
+ aws_error_code: Optional[str] = None,
+ aws_error_message: Optional[str] = None,
+ aws_request_id: Optional[str] = None,
+ error_source: Optional[str] = None,
) -> None:
"""Initialize AWS exception.
Args:
message: Human-readable error message
details: Additional error details and context
- error_code: Specific error code for programmatic handling
+ error_code: Domain-level error code for programmatic handling
+ aws_error_code: Original AWS API error code (e.g. UnauthorizedOperation)
+ aws_error_message: Original AWS API error message
+ aws_request_id: AWS request ID for Support cases
+ error_source: AWS service.operation that failed (e.g. aws.ec2.RunInstances)
"""
super().__init__(message, error_code or self.__class__.__name__, details)
self.error_code = error_code or self.__class__.__name__
+ self.aws_error_code = aws_error_code
+ self.aws_error_message = aws_error_message
+ self.aws_request_id = aws_request_id
+ self.error_source = error_source
def to_dict(self) -> dict[str, Any]:
"""Convert error to dictionary."""
result: dict[str, Any] = super().to_dict() # type: ignore[attr-defined]
if self.error_code and self.error_code != self.__class__.__name__:
result["error_code"] = self.error_code
+ if self.aws_error_code:
+ result["aws_error_code"] = self.aws_error_code
+ if self.aws_error_message:
+ result["aws_error_message"] = self.aws_error_message
+ if self.aws_request_id:
+ result["aws_request_id"] = self.aws_request_id
+ if self.error_source:
+ result["error_source"] = self.error_source
return result
diff --git a/src/orb/providers/aws/health.py b/src/orb/providers/aws/health.py
index c4c57ddea..899da488a 100644
--- a/src/orb/providers/aws/health.py
+++ b/src/orb/providers/aws/health.py
@@ -12,12 +12,25 @@
from orb.providers.aws.infrastructure.aws_client import AWSClient
-def register_aws_health_checks(health_check: HealthCheckPort, aws_client: "AWSClient") -> None:
+def register_aws_health_checks(
+ health_check: HealthCheckPort,
+ aws_client: "AWSClient",
+ storage_strategy: str = "json",
+) -> None:
"""Register AWS-specific health checks with the given HealthCheck instance.
+ The ``aws`` (STS) and ``ec2`` checks are always registered because they
+ validate core AWS connectivity. The ``dynamodb`` check is only registered
+ when the configured storage backend is ``"dynamodb"``; registering it
+ unconditionally causes ``/health`` to return 503 when the default
+ file/JSON storage is in use because the ``dynamodb:ListTables`` call
+ fails with no credentials or permissions.
+
Args:
health_check: The application HealthCheckPort to register checks on.
aws_client: Authenticated AWS client used by the checks.
+ storage_strategy: The configured storage backend (e.g. ``"json"``,
+ ``"sql"``, ``"dynamodb"``). Defaults to ``"json"``.
"""
def _check_aws_health() -> HealthStatus:
@@ -85,4 +98,5 @@ def _check_dynamodb_health() -> HealthStatus:
health_check.register_check("aws", _check_aws_health)
health_check.register_check("ec2", _check_ec2_health)
- health_check.register_check("dynamodb", _check_dynamodb_health)
+ if storage_strategy == "dynamodb":
+ health_check.register_check("dynamodb", _check_dynamodb_health)
diff --git a/src/orb/providers/aws/infrastructure/handlers/asg/capacity_manager.py b/src/orb/providers/aws/infrastructure/handlers/asg/capacity_manager.py
index 367ec30ed..52cc41d9a 100644
--- a/src/orb/providers/aws/infrastructure/handlers/asg/capacity_manager.py
+++ b/src/orb/providers/aws/infrastructure/handlers/asg/capacity_manager.py
@@ -5,8 +5,10 @@
1. Pre-termination capacity reduction — lower DesiredCapacity and MinSize so
AWS does not replace the instances we are about to terminate.
-2. Instance detachment with capacity decrement — detach specific instances from
- a known ASG, lower MinSize if needed, then terminate.
+2. Instance termination with weight-aware capacity decrement — call
+ terminate_instance_in_auto_scaling_group per instance so AWS decrements
+ DesiredCapacity by the instance's WeightedCapacity (not by 1), which is
+ symmetric with scale-up behaviour in Mixed Instance Policy ASGs.
"""
from typing import Any, Callable, Optional
@@ -128,11 +130,13 @@ def reduce_capacity(self, instance_ids: list[str]) -> None:
def release_instances(
self, asg_name: str, instance_ids: list[str], asg_details: dict[str, Any]
) -> None:
- """Detach instances from an ASG, adjust MinSize if needed, then terminate.
+ """Terminate instances via the ASG API, adjusting MinSize if needed.
+ Uses terminate_instance_in_auto_scaling_group so DesiredCapacity is
+ decremented by WeightedCapacity (weight-aware, unlike detach_instances).
If asg_details is empty the instances are terminated directly without
attempting ASG-specific operations. When DesiredCapacity reaches zero
- after detachment the ASG and its associated launch template are deleted.
+ the ASG and its associated launch template are deleted.
"""
self._logger.info("Processing ASG %s with %s instances", asg_name, len(instance_ids))
@@ -172,21 +176,61 @@ def release_instances(
self._cleanup_on_zero_capacity("asg", asg_name)
return
- # Detach instances (API limit: 50 per call; use 20 for safety)
- for chunk in self._chunk_list(instance_ids, 20):
+ # Guard against double-execution: only terminate instances that are currently
+ # members of this ASG. If release_instances is called a second time for the
+ # same set (e.g. due to an IN_PROGRESS status retry or a race), the instances
+ # are already gone and this check prevents a second DesiredCapacity decrement.
+ instances_to_terminate = self._filter_asg_members(asg_name, instance_ids)
+ if not instances_to_terminate:
+ self._logger.info(
+ "ASG %s: all %d instance(s) already terminated/detached — skipping ASG termination",
+ asg_name,
+ len(instance_ids),
+ )
+ # Instances may still be running (standalone after a prior partial operation);
+ # terminate them directly so the return request can complete.
+ self._aws_ops.terminate_instances_with_fallback(
+ instance_ids, self._request_adapter, f"ASG {asg_name} instances (already detached)"
+ )
+ return
+
+ skipped = [i for i in instance_ids if i not in instances_to_terminate]
+ if skipped:
+ self._logger.info(
+ "ASG %s: %d instance(s) already terminated/detached (skipping): %s",
+ asg_name,
+ len(skipped),
+ skipped,
+ )
+
+ # Use terminate_instance_in_auto_scaling_group (one instance per API call).
+ # Unlike detach_instances — which decrements DesiredCapacity by the *count*
+ # of instances (always 1 per instance regardless of weight) — this API
+ # decrements DesiredCapacity by the instance's WeightedCapacity, which is
+ # symmetric with scale-up in Mixed Instance Policy ASGs. It also handles
+ # both the termination and the capacity decrement atomically, so no
+ # separate EC2 terminate step is needed.
+ for instance_id in instances_to_terminate:
self._retry_with_backoff(
- self._aws_client.autoscaling_client.detach_instances,
+ self._aws_client.autoscaling_client.terminate_instance_in_auto_scaling_group,
operation_type="critical",
- AutoScalingGroupName=asg_name,
- InstanceIds=chunk,
+ InstanceId=instance_id,
ShouldDecrementDesiredCapacity=True,
)
- self._logger.debug("Detached chunk from ASG %s: %s", asg_name, chunk)
- self._logger.info("Detached instances from ASG %s: %s", asg_name, instance_ids)
+ self._logger.debug(
+ "Terminated instance %s in ASG %s with weight-aware capacity decrement",
+ instance_id,
+ asg_name,
+ )
+ self._logger.info(
+ "Terminated instances in ASG %s (weight-aware): %s", asg_name, instances_to_terminate
+ )
- # Re-describe the ASG to get the live DesiredCapacity after detach, since
- # ShouldDecrementDesiredCapacity=True may have already decremented it in AWS
- # and the value in asg_details is now stale.
+ # Re-describe the ASG to get live state after termination.
+ # terminate_instance_in_auto_scaling_group decrements DesiredCapacity by
+ # WeightedCapacity, so DesiredCapacity == 0 reliably signals an empty fleet
+ # for both unweighted and weighted ASGs. No secondary instance-list check needed.
+ live_desired = 0
try:
live_response = self._retry_with_backoff(
self._aws_client.autoscaling_client.describe_auto_scaling_groups,
@@ -194,14 +238,15 @@ def release_instances(
AutoScalingGroupNames=[asg_name],
)
live_groups = live_response.get("AutoScalingGroups", [])
- live_desired = live_groups[0].get("DesiredCapacity", 0) if live_groups else 0
+ if live_groups:
+ live_desired = live_groups[0].get("DesiredCapacity", 0) or 0
except Exception as exc:
self._logger.warning(
- "Failed to re-describe ASG %s after detach, falling back to computed capacity: %s",
+ "Failed to re-describe ASG %s after termination, falling back to computed capacity: %s",
asg_name,
exc,
)
- live_desired = max(0, asg_details["DesiredCapacity"] - len(instance_ids))
+ live_desired = max(0, asg_details["DesiredCapacity"] - len(instances_to_terminate))
new_capacity = max(0, live_desired)
@@ -214,13 +259,8 @@ def release_instances(
)
self._logger.info("Reduced ASG %s MinSize to %s", asg_name, new_capacity)
- self._aws_ops.terminate_instances_with_fallback(
- instance_ids, self._request_adapter, f"ASG {asg_name} instances"
- )
- self._logger.info("Terminated ASG %s instances: %s", asg_name, instance_ids)
-
if new_capacity == 0:
- self._logger.info("ASG %s capacity is zero, deleting ASG", asg_name)
+ self._logger.info("ASG %s is empty, deleting ASG", asg_name)
self._call_delete_asg(asg_name)
self._cleanup_on_zero_capacity("asg", asg_name)
@@ -228,6 +268,66 @@ def release_instances(
# Private helpers
# ------------------------------------------------------------------
+ def _filter_asg_members(self, asg_name: str, instance_ids: list[str]) -> list[str]:
+ """Return the subset of instance_ids that are currently attached to asg_name.
+
+ Used as an idempotency guard in release_instances: if an instance has already
+ been terminated or detached (e.g. by a prior call), it is excluded so that
+ ShouldDecrementDesiredCapacity=True is not applied a second time.
+ """
+ if not instance_ids:
+ return []
+ try:
+ response = self._retry_with_backoff(
+ self._aws_client.autoscaling_client.describe_auto_scaling_instances,
+ operation_type="read_only",
+ InstanceIds=instance_ids,
+ )
+ entries = response.get("AutoScalingInstances", [])
+ # If describe returned no entries, the API call did not give us
+ # actionable membership info — fall back to processing all
+ # instances rather than silently dropping them. This matches the
+ # exception-path behaviour below.
+ if not entries:
+ return list(instance_ids)
+ # Only terminate instances that are currently in a state where
+ # ShouldDecrementDesiredCapacity=True is meaningful. Instances in
+ # Detaching / Detached / Terminated already had their DesiredCapacity
+ # decremented on the first call; including them again would double-count.
+ _DETACHABLE_STATES = {"InService", "Standby"}
+
+ # Map: instance_id → lifecycle_state for every entry belonging to asg_name.
+ state_by_id = {
+ entry["InstanceId"]: entry.get("LifecycleState", "")
+ for entry in entries
+ if entry.get("AutoScalingGroupName") == asg_name
+ }
+
+ if not state_by_id:
+ # None of the requested instances appeared in describe output at all.
+ # Could mean: (a) instances were never in this ASG, (b) describe had a
+ # gap. Fall back to processing all instances to be safe — the original
+ # guard logic below will fall through to terminate_instances_with_fallback.
+ return list(instance_ids)
+
+ # Return only the instances in a detachable lifecycle state.
+ filtered = [iid for iid in instance_ids if state_by_id.get(iid) in _DETACHABLE_STATES]
+ # instances not in state_by_id are not (or no longer) in this ASG;
+ # they can be skipped (no longer need weight-aware termination).
+ return filtered
+ except Exception as exc:
+ self._logger.warning(
+ "Failed to verify ASG %s membership for instances %s; "
+ "proceeding with all instances to avoid leaving them running: %s",
+ asg_name,
+ instance_ids,
+ exc,
+ )
+ # On error, be conservative: attempt termination for all instances.
+ # A "not a member" error from terminate_instance_in_auto_scaling_group
+ # is better than skipping a needed capacity decrement.
+ return list(instance_ids)
+
def _call_delete_asg(self, asg_name: str) -> None:
"""Invoke the registered delete-ASG callback, or fall back to direct deletion."""
if self._delete_asg_fn is not None:
diff --git a/src/orb/providers/aws/infrastructure/handlers/asg/handler.py b/src/orb/providers/aws/infrastructure/handlers/asg/handler.py
index 26b3aae51..f147d3bd6 100644
--- a/src/orb/providers/aws/infrastructure/handlers/asg/handler.py
+++ b/src/orb/providers/aws/infrastructure/handlers/asg/handler.py
@@ -33,6 +33,7 @@
from orb.domain.base.dependency_injection import injectable
from orb.domain.base.ports import LoggingPort
from orb.domain.base.ports.configuration_port import ConfigurationPort
+from orb.domain.base.provider_fulfilment import CheckHostsStatusResult, ProviderFulfilment
from orb.domain.request.aggregate import Request
from orb.domain.template.template_aggregate import Template
from orb.infrastructure.adapters.ports.request_adapter_port import RequestAdapterPort
@@ -520,36 +521,235 @@ def _group_details_key(self) -> str:
def _grouping_label(self) -> str:
return "ASG"
- def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
- """Check the status of instances across all ASGs in the request."""
+ def check_hosts_status(self, request: Request) -> CheckHostsStatusResult:
+ """Check the status of instances across all ASGs in the request.
+
+ Fulfilment semantics:
+ sum(WeightedCapacity for InService instances) >= DesiredCapacity
+ AND pending_count == 0 AND failed_count == 0 → fulfilled.
+
+ For ASGs without WeightedCapacity (uniform instance types) each
+ InService instance contributes 1 unit.
+ """
try:
if not request.resource_ids:
self._logger.info("No ASG names found in request")
- return []
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="No ASG names yet — waiting for provisioning",
+ target_units=request.requested_count,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
- all_instances = []
+ all_instances: list[dict[str, Any]] = []
+ asg_results: list[CheckHostsStatusResult] = []
- # Process all ASG names instead of just the first one
for asg_name in request.resource_ids:
try:
- asg_instances = self._get_asg_instances(
+ result = self._get_asg_status(
asg_name,
request_id=str(request.request_id),
- resource_id=asg_name,
+ requested_count=request.requested_count,
)
- if asg_instances:
- formatted_instances = self._format_instance_data(
- asg_instances, asg_name, self._resolve_provider_api(request)
- )
- all_instances.extend(formatted_instances)
+ all_instances.extend(result.instances)
+ asg_results.append(result)
except Exception as e:
self._logger.error("Failed to get instances for ASG %s: %s", asg_name, e)
continue
- return all_instances
+
+ if not asg_results:
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="No ASG status available — will retry",
+ ),
+ )
+
+ if len(asg_results) == 1:
+ return CheckHostsStatusResult(
+ instances=all_instances,
+ fulfilment=asg_results[0].fulfilment,
+ )
+
+ states = [r.fulfilment.state for r in asg_results]
+ if all(s == "fulfilled" for s in states):
+ combined_state = "fulfilled"
+ combined_msg = f"All {len(asg_results)} ASGs fulfilled"
+ elif any(s == "failed" for s in states):
+ combined_state = "failed"
+ combined_msg = "One or more ASGs failed"
+ elif any(s == "partial" for s in states):
+ combined_state = "partial"
+ combined_msg = "One or more ASGs partially fulfilled"
+ else:
+ combined_state = "in_progress"
+ combined_msg = "Waiting for ASG(s) to fulfil"
+
+ return CheckHostsStatusResult(
+ instances=all_instances,
+ fulfilment=ProviderFulfilment(state=combined_state, message=combined_msg),
+ )
+
except Exception as e:
self._logger.error("Unexpected error checking ASG status: %s", str(e))
raise AWSInfrastructureError(f"Failed to check ASG status: {e!s}")
+ def _get_asg_status(
+ self,
+ asg_name: str,
+ request_id: str = "",
+ requested_count: int = 1,
+ ) -> CheckHostsStatusResult:
+ """Get status + fulfilment for a specific ASG."""
+ response = self._retry_with_backoff(
+ self.aws_client.autoscaling_client.describe_auto_scaling_groups,
+ operation_type="standard",
+ AutoScalingGroupNames=[asg_name],
+ )
+ groups = response.get("AutoScalingGroups", [])
+ if not groups:
+ self._logger.warning("ASG %s not found", asg_name)
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message=f"ASG {asg_name} not yet visible — waiting",
+ target_units=requested_count,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
+
+ group = groups[0]
+ desired_capacity: int = group.get("DesiredCapacity") or 0
+ raw_instances = group.get("Instances") or []
+
+ # Compute weighted fulfilment from ASG Instances list
+ in_service_weighted = sum(
+ int(inst.get("WeightedCapacity") or 1)
+ for inst in raw_instances
+ if inst.get("LifecycleState") == "InService"
+ )
+ pending_raw = [
+ inst
+ for inst in raw_instances
+ if inst.get("LifecycleState") in ("Pending", "Pending:Wait", "Pending:Proceed")
+ ]
+ pending_count = len(pending_raw)
+ in_service_count = sum(
+ 1 for inst in raw_instances if inst.get("LifecycleState") == "InService"
+ )
+
+ # Get full EC2 instance details for instances in the ASG
+ instance_ids = [
+ inst["InstanceId"]
+ for inst in raw_instances
+ if inst.get("InstanceId")
+ and inst.get("LifecycleState")
+ not in ("Terminating", "Terminated", "Detaching", "Detached")
+ ]
+
+ if not instance_ids:
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=self._compute_asg_fulfilment(
+ desired_capacity=desired_capacity,
+ in_service_weighted=in_service_weighted,
+ pending_count=pending_count,
+ in_service_count=in_service_count,
+ ec2_instances=[],
+ ),
+ )
+
+ try:
+ instance_details = self._get_instance_details(
+ instance_ids,
+ request_id=request_id,
+ resource_id=asg_name,
+ provider_api="ASG",
+ )
+ except Exception as e:
+ self._logger.warning("Failed to describe EC2 instances for ASG %s: %s", asg_name, e)
+ instance_details = []
+
+ provider_api_value = (getattr(self, "metadata", {}) or {}).get("provider_api", "ASG")
+ formatted = (
+ self._format_instance_data(instance_details, asg_name, provider_api_value)
+ if instance_details
+ else []
+ )
+
+ fulfilment = self._compute_asg_fulfilment(
+ desired_capacity=desired_capacity,
+ in_service_weighted=in_service_weighted,
+ pending_count=pending_count,
+ in_service_count=in_service_count,
+ ec2_instances=formatted,
+ )
+ return CheckHostsStatusResult(instances=formatted, fulfilment=fulfilment)
+
+ def _compute_asg_fulfilment(
+ self,
+ desired_capacity: int,
+ in_service_weighted: int,
+ pending_count: int,
+ in_service_count: int,
+ ec2_instances: list[dict[str, Any]],
+ ) -> ProviderFulfilment:
+ """Compute ProviderFulfilment for an ASG request.
+
+ sum(WeightedCapacity for InService) >= DesiredCapacity AND
+ pending_count == 0 AND failed_count == 0 → fulfilled.
+ """
+ failed_count = sum(1 for i in ec2_instances if i.get("status") in ("failed", "error"))
+ running_count = sum(1 for i in ec2_instances if i.get("status") == "running")
+
+ asg_fully_fulfilled = desired_capacity > 0 and in_service_weighted >= desired_capacity
+
+ if asg_fully_fulfilled and pending_count == 0 and failed_count == 0:
+ return ProviderFulfilment(
+ state="fulfilled",
+ message=(
+ f"ASG fulfilled: {in_service_count} instance(s) InService "
+ f"({in_service_weighted}/{desired_capacity} weighted capacity)"
+ ),
+ target_units=desired_capacity,
+ fulfilled_units=in_service_weighted,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif failed_count > 0 and in_service_count == 0 and pending_count == 0:
+ return ProviderFulfilment(
+ state="failed",
+ message=f"ASG failed: {failed_count} instance(s) in failure state",
+ target_units=desired_capacity,
+ fulfilled_units=in_service_weighted,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ else:
+ return ProviderFulfilment(
+ state="in_progress",
+ message=(
+ f"ASG: {in_service_count} InService, {pending_count} pending "
+ f"({in_service_weighted}/{desired_capacity} weighted capacity)"
+ ),
+ target_units=desired_capacity,
+ fulfilled_units=in_service_weighted,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+
@classmethod
def get_example_templates(cls) -> list[Template]:
"""Get example templates for ASG handler."""
@@ -560,7 +760,7 @@ def get_example_templates(cls) -> list[Template]:
description="Auto Scaling Group with on-demand instances only",
provider_api="ASG",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=15,
+ max_instances=100,
price_type="ondemand",
subnet_ids=[],
security_group_ids=[],
@@ -572,7 +772,7 @@ def get_example_templates(cls) -> list[Template]:
description="Auto Scaling Group with spot instances only",
provider_api="ASG",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=20,
+ max_instances=100,
price_type="spot",
max_price=0.10,
subnet_ids=[],
@@ -585,7 +785,7 @@ def get_example_templates(cls) -> list[Template]:
description="Auto Scaling Group with mixed on-demand and spot instances",
provider_api="ASG",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=25,
+ max_instances=100,
price_type="heterogeneous",
percent_on_demand=30,
allocation_strategy="lowestPrice",
diff --git a/src/orb/providers/aws/infrastructure/handlers/base_handler.py b/src/orb/providers/aws/infrastructure/handlers/base_handler.py
index e0a8d0c9f..c9e74bc59 100644
--- a/src/orb/providers/aws/infrastructure/handlers/base_handler.py
+++ b/src/orb/providers/aws/infrastructure/handlers/base_handler.py
@@ -8,15 +8,19 @@
import asyncio
import json
+import re
from abc import ABC, abstractmethod
from datetime import datetime
-from typing import Any, Callable, Optional, TypeVar
+from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
from botocore.exceptions import ClientError
from orb.application.base.provider_handlers import BaseProviderHandler
from orb.domain.base.dependency_injection import injectable
from orb.domain.base.exceptions import InfrastructureError
+
+if TYPE_CHECKING:
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
from orb.domain.base.ports import ErrorHandlingPort, LoggingPort
from orb.domain.base.ports.configuration_port import ConfigurationPort
from orb.domain.request.aggregate import Request
@@ -165,15 +169,21 @@ def _acquire_hosts_internal(
"""
@abstractmethod
- def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
- """
- Check the status of hosts for a request.
+ def check_hosts_status(self, request: Request) -> "CheckHostsStatusResult":
+ """Check the status of hosts for a request.
+
+ Returns a ``CheckHostsStatusResult`` containing both per-instance
+ detail dicts and the provider's ``ProviderFulfilment`` verdict.
+
+ Every concrete handler MUST emit a ``ProviderFulfilment`` — no
+ fallback to count-based logic is permitted. The application layer
+ raises ``ProviderContractError`` if fulfilment is absent.
Args:
request: The request to check
Returns:
- List of instance details
+ CheckHostsStatusResult with instances and fulfilment verdict.
Raises:
AWSEntityNotFoundError: If the AWS resource is not found
@@ -443,15 +453,41 @@ def _get_retry_strategy_config(
"max_delay": 30.0,
}
+ # Pattern matching AWS temporary credential access key prefixes — strip if
+ # accidentally included in an error message.
+ _ACCESS_KEY_RE = re.compile(r"A[KS]IA[A-Z0-9]{16}", re.ASCII)
+
+ @classmethod
+ def _redact_aws_message(cls, message: str) -> str:
+ """Remove access-key-like tokens from an AWS error message."""
+ return cls._ACCESS_KEY_RE.sub("[REDACTED]", message)
+
def _convert_client_error(
self, error: ClientError, operation_name: str = "unknown"
) -> Exception:
- """Convert AWS ClientError to domain exception."""
+ """Convert AWS ClientError to domain exception, preserving AWS error details."""
error_code = error.response["Error"]["Code"]
- error_message = error.response["Error"]["Message"]
+ raw_message = error.response["Error"]["Message"]
+ error_message = self._redact_aws_message(raw_message)
+
+ # Derive the service name from the handler class for error_source attribution.
+ service_label = self._get_service_name()
+ error_source = f"aws.{service_label}.{operation_name}"
+
+ # Extract the AWS request ID if present (useful for AWS Support cases).
+ response_metadata = error.response.get("ResponseMetadata", {})
+ aws_request_id: str | None = response_metadata.get("RequestId")
+
+ # Common kwargs carried into every domain exception subclass.
+ aws_kwargs: dict[str, Any] = {
+ "aws_error_code": error_code,
+ "aws_error_message": error_message,
+ "aws_request_id": aws_request_id,
+ "error_source": error_source,
+ }
if error_code in ["ValidationError", "InvalidParameterValue"]:
- return AWSValidationError(error_message)
+ return AWSValidationError(error_message, **aws_kwargs)
elif error_code in [
"LimitExceeded",
"InstanceLimitExceeded",
@@ -460,19 +496,24 @@ def _convert_client_error(
"ServiceQuotaExceededException",
"ResourceCountExceeded",
]:
- return QuotaExceededError(error_message)
+ return QuotaExceededError(error_message, **aws_kwargs)
elif error_code == "ResourceInUse":
- return ResourceInUseError(error_message)
+ return ResourceInUseError(error_message, **aws_kwargs)
elif error_code in ["UnauthorizedOperation", "AccessDenied"]:
- return AuthorizationError(error_message)
+ return AuthorizationError(error_message, **aws_kwargs)
elif error_code == "RequestLimitExceeded":
- return RateLimitError(error_message)
+ return RateLimitError(error_message, **aws_kwargs)
elif error_code in ["ResourceNotFound", "InvalidInstanceID.NotFound"]:
- return AWSEntityNotFoundError(error_message)
+ return AWSEntityNotFoundError(error_message, **aws_kwargs)
elif error_code in ["RequestTimeout", "ServiceUnavailable"]:
- return NetworkError(error_message)
+ return NetworkError(error_message, **aws_kwargs)
else:
- return InfrastructureError(f"AWS Error: {error_code} - {error_message}")
+ from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError
+
+ return AWSInfrastructureError(
+ f"AWS Error: {error_code} - {error_message}",
+ **aws_kwargs,
+ )
def _paginate(self, client_method: Callable, result_key: str, **kwargs) -> list[dict[str, Any]]:
"""
diff --git a/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/handler.py b/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/handler.py
index a7c0fbaeb..507e2bcd5 100644
--- a/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/handler.py
+++ b/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/handler.py
@@ -33,13 +33,14 @@
from orb.domain.base.dependency_injection import injectable
from orb.domain.base.ports import LoggingPort
from orb.domain.base.ports.configuration_port import ConfigurationPort
+from orb.domain.base.provider_fulfilment import CheckHostsStatusResult, ProviderFulfilment
from orb.domain.request.aggregate import Request
from orb.domain.template.template_aggregate import Template
from orb.infrastructure.adapters.ports.request_adapter_port import RequestAdapterPort
from orb.infrastructure.error.decorators import handle_infrastructure_exceptions
from orb.infrastructure.resilience import CircuitBreakerOpenError
from orb.providers.aws.domain.template.aws_template_aggregate import AWSTemplate
-from orb.providers.aws.domain.template.value_objects import AWSAllocationStrategy, AWSFleetType
+from orb.providers.aws.domain.template.value_objects import AWSFleetType
from orb.providers.aws.exceptions.aws_exceptions import (
AWSEntityNotFoundError,
AWSInfrastructureError,
@@ -53,11 +54,15 @@
EC2FleetReleaseManager,
)
from orb.providers.aws.infrastructure.handlers.shared.base_context_mixin import BaseContextMixin
+from orb.providers.aws.infrastructure.handlers.shared.fleet_fulfilment import (
+ compute_capacity_based_fulfilment,
+)
from orb.providers.aws.infrastructure.handlers.shared.fleet_grouping_mixin import FleetGroupingMixin
from orb.providers.aws.infrastructure.launch_template.manager import (
AWSLaunchTemplateManager,
)
from orb.providers.aws.utilities.aws_operations import AWSOperations
+from orb.providers.aws.value_objects import AWSAllocationStrategy
@injectable
@@ -153,7 +158,9 @@ def _acquire_hosts_internal(
# lazily by the check_hosts_status / _check_single_fleet_status polling path.
instance_ids = fleet_result.get("instance_ids", [])
if instance_ids:
- instances = [{"instance_id": iid} for iid in instance_ids]
+ instances = [
+ {"instance_id": iid, "resource_id": fleet_id} for iid in instance_ids
+ ]
self._logger.info(
"EC2Fleet instant fleet created with %d instance(s): %s",
len(instance_ids),
@@ -372,24 +379,71 @@ def _create_fleet_config(
lt_version=launch_template_version,
)
- def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
- """Check the status of instances in the fleet."""
+ def check_hosts_status(self, request: Request) -> CheckHostsStatusResult:
+ """Check the status of instances in the fleet.
+
+ Fulfilment semantics (per fleet type):
+ - Instant: same as RunInstances — running_count >= requested_count
+ and failed_count == 0 → fulfilled. ``fulfillment_final=True`` so
+ partial/failed can be detected when pending reaches zero.
+ - Maintain / Request: FulfilledCapacity >= TargetCapacity AND
+ pending_count == 0 AND failed_count == 0 → fulfilled. This is the
+ weighted-fleet path that fixes the live test timeout.
+ """
self._logger.debug(f" check_hosts_status {request}")
if not request.resource_ids:
raise AWSInfrastructureError("No Fleet ID found in request")
- all_results: list[dict] = []
+ all_instances: list[dict] = []
+ # For multi-fleet requests collect instances; compute combined fulfilment at the end.
+ fleet_results: list[CheckHostsStatusResult] = []
for fleet_id in request.resource_ids:
try:
- results = self._check_single_fleet_status(fleet_id, request)
- all_results.extend(results)
+ result = self._check_single_fleet_status(fleet_id, request)
+ all_instances.extend(result.instances)
+ fleet_results.append(result)
except Exception as e:
self._logger.warning(
"Failed to check status for fleet %s, skipping: %s", fleet_id, e
)
- return all_results
- def _check_single_fleet_status(self, fleet_id: str, request: Request) -> list[dict]:
+ if not fleet_results:
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="No fleet status available — will retry",
+ ),
+ )
+
+ # For a single fleet (typical case) return its result directly.
+ if len(fleet_results) == 1:
+ return CheckHostsStatusResult(
+ instances=all_instances,
+ fulfilment=fleet_results[0].fulfilment,
+ )
+
+ # Multiple fleets: aggregate — all must be fulfilled for overall fulfilled.
+ states = [r.fulfilment.state for r in fleet_results]
+ if all(s == "fulfilled" for s in states):
+ combined_state = "fulfilled"
+ combined_msg = f"All {len(fleet_results)} fleets fulfilled"
+ elif any(s == "failed" for s in states):
+ combined_state = "failed"
+ combined_msg = "One or more fleets failed"
+ elif any(s == "partial" for s in states):
+ combined_state = "partial"
+ combined_msg = "One or more fleets partially fulfilled"
+ else:
+ combined_state = "in_progress"
+ combined_msg = "Waiting for fleet(s) to fulfil"
+
+ return CheckHostsStatusResult(
+ instances=all_instances,
+ fulfilment=ProviderFulfilment(state=combined_state, message=combined_msg),
+ )
+
+ def _check_single_fleet_status(self, fleet_id: str, request: Request) -> CheckHostsStatusResult:
"""Check the status of instances in a single fleet."""
try:
fleet_type_value = request.metadata.get("fleet_type")
@@ -434,11 +488,16 @@ def _check_single_fleet_status(self, fleet_id: str, request: Request) -> list[di
self._logger.debug(f" check_hosts_status final fleet_type: {fleet_type}")
+ # Read capacity data from DescribeFleets (already called above — no extra API call)
+ spec = fleet.get("TargetCapacitySpecification") or {}
+ target_capacity = spec.get("TotalTargetCapacity")
+ fulfilled_capacity = fleet.get("FulfilledCapacity") or 0
+
self._logger.debug(
"Fleet status: %s, Target capacity: %s, Fulfilled capacity: %s",
fleet.get("FleetState"),
- fleet.get("TargetCapacitySpecification", {}).get("TotalTargetCapacity"),
- fleet.get("FulfilledCapacity", 0),
+ target_capacity,
+ fulfilled_capacity,
)
instance_ids = []
@@ -478,7 +537,14 @@ def _check_single_fleet_status(self, fleet_id: str, request: Request) -> list[di
if not instance_ids:
self._logger.info("No active instances found in fleet %s", fleet_id)
- return []
+ fulfilment = self._compute_ec2fleet_fulfilment(
+ fleet_type=fleet_type,
+ instances=[],
+ target_capacity=target_capacity,
+ fulfilled_capacity=fulfilled_capacity,
+ requested_count=request.requested_count,
+ )
+ return CheckHostsStatusResult(instances=[], fulfilment=fulfilment)
instance_details = self._get_instance_details(
instance_ids,
@@ -486,9 +552,17 @@ def _check_single_fleet_status(self, fleet_id: str, request: Request) -> list[di
resource_id=fleet_id,
provider_api="EC2Fleet",
)
- return self._format_instance_data(
+ instances = self._format_instance_data(
instance_details, fleet_id, self._resolve_provider_api(request)
)
+ fulfilment = self._compute_ec2fleet_fulfilment(
+ fleet_type=fleet_type,
+ instances=instances,
+ target_capacity=target_capacity,
+ fulfilled_capacity=fulfilled_capacity,
+ requested_count=request.requested_count,
+ )
+ return CheckHostsStatusResult(instances=instances, fulfilment=fulfilment)
except ClientError as e:
error = self._convert_client_error(e)
@@ -498,6 +572,90 @@ def _check_single_fleet_status(self, fleet_id: str, request: Request) -> list[di
self._logger.error("Unexpected error checking EC2 Fleet status: %s", str(e))
raise AWSInfrastructureError(f"Failed to check EC2 Fleet status: {e!s}")
+ def _compute_ec2fleet_fulfilment(
+ self,
+ fleet_type: Optional[AWSFleetType],
+ instances: list[dict[str, Any]],
+ target_capacity: Optional[int],
+ fulfilled_capacity: float,
+ requested_count: int,
+ ) -> ProviderFulfilment:
+ """Compute ProviderFulfilment for an EC2 Fleet request.
+
+ Instant fleets: running_count >= requested_count — same as RunInstances.
+ Maintain/Request fleets: FulfilledCapacity >= TargetCapacity AND
+ pending_count == 0 AND failed_count == 0.
+ """
+ running_count = sum(1 for i in instances if i.get("status") == "running")
+ pending_count = sum(1 for i in instances if i.get("status") in ("pending", "starting"))
+ failed_count = sum(1 for i in instances if i.get("status") in ("failed", "error"))
+ target_units = target_capacity if target_capacity is not None else requested_count
+
+ if fleet_type == AWSFleetType.INSTANT:
+ # Instant fleet: synchronous result, count-based (same as RunInstances)
+ if running_count >= requested_count and failed_count == 0:
+ return ProviderFulfilment(
+ state="fulfilled",
+ message=f"Instant fleet: {running_count} instance(s) running",
+ target_units=target_units,
+ fulfilled_units=running_count,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif pending_count > 0:
+ return ProviderFulfilment(
+ state="in_progress",
+ message=f"Instant fleet: {running_count}/{requested_count} running, {pending_count} pending",
+ target_units=target_units,
+ fulfilled_units=running_count,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ else:
+ # fulfillment_final=True for instant — no more instances coming
+ if running_count > 0:
+ return ProviderFulfilment(
+ state="partial",
+ message=f"Instant fleet: {running_count}/{requested_count} instance(s) running",
+ target_units=target_units,
+ fulfilled_units=running_count,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif not instances:
+ return ProviderFulfilment(
+ state="in_progress",
+ message="Instant fleet: waiting for instances",
+ target_units=target_units,
+ fulfilled_units=0,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ )
+ else:
+ return ProviderFulfilment(
+ state="failed",
+ message="Instant fleet: all instances failed",
+ target_units=target_units,
+ fulfilled_units=0,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ else:
+ # Maintain / Request fleet: capacity-unit based fulfilment
+ return compute_capacity_based_fulfilment(
+ target_capacity=target_capacity,
+ fulfilled_capacity=fulfilled_capacity,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ provider_label="Fleet",
+ )
+
def release_hosts(
self,
machine_ids: list[str],
@@ -729,7 +887,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with instant fulfillment using on-demand instances",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=10,
+ max_instances=100,
price_type="ondemand",
subnet_ids=[],
security_group_ids=[],
@@ -742,7 +900,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with instant fulfillment using spot instances",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=10,
+ max_instances=100,
price_type="spot",
max_price=0.10,
subnet_ids=[],
@@ -756,7 +914,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with instant fulfillment using mixed pricing",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=10,
+ max_instances=100,
price_type="heterogeneous",
percent_on_demand=30,
allocation_strategy="diversified",
@@ -773,7 +931,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with request fulfillment using on-demand instances",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=15,
+ max_instances=100,
price_type="ondemand",
subnet_ids=[],
security_group_ids=[],
@@ -786,7 +944,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with request fulfillment using spot instances",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=20,
+ max_instances=100,
price_type="spot",
allocation_strategy="capacityOptimized",
max_price=0.10,
@@ -801,7 +959,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with request fulfillment using mixed pricing",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=25,
+ max_instances=100,
price_type="heterogeneous",
percent_on_demand=40,
allocation_strategy="diversified",
@@ -819,7 +977,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with maintain capacity using on-demand instances",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=12,
+ max_instances=100,
price_type="ondemand",
subnet_ids=[],
security_group_ids=[],
@@ -832,7 +990,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with maintain capacity using spot instances",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.xlarge": 4},
- max_instances=30,
+ max_instances=100,
price_type="spot",
allocation_strategy="priceCapacityOptimized",
max_price=0.10,
@@ -847,7 +1005,7 @@ def get_example_templates(cls) -> list[Template]:
description="EC2 Fleet with maintain capacity using mixed pricing",
provider_api="EC2Fleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=50,
+ max_instances=100,
price_type="heterogeneous",
percent_on_demand=50,
allocation_strategy="capacityOptimized",
diff --git a/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/release_manager.py b/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/release_manager.py
index c9e21ae79..0c9d60b18 100644
--- a/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/release_manager.py
+++ b/src/orb/providers/aws/infrastructure/handlers/ec2_fleet/release_manager.py
@@ -118,20 +118,26 @@ def release(
)
if instance_ids:
+ weighted_capacity_to_return = self._sum_weighted_capacity(
+ fleet_id, fleet_details, instance_ids
+ )
+
decision = compute_fleet_release_decision(
fleet_type=fleet_type,
current_capacity=current_capacity,
- instances_to_return=len(instance_ids),
+ weighted_capacity_to_return=weighted_capacity_to_return,
)
if decision.requires_capacity_reduction:
- new_capacity = max(0, current_capacity - len(instance_ids))
+ new_capacity = max(0, current_capacity - weighted_capacity_to_return)
self._logger.info(
- "Reducing %s fleet %s capacity from %s to %s before terminating instances",
+ "Reducing %s fleet %s capacity from %s to %s "
+ "(weighted_capacity_to_return=%s) before terminating instances",
fleet_type,
fleet_id,
current_capacity,
new_capacity,
+ weighted_capacity_to_return,
)
self._retry(
self._aws_client.ec2_client.modify_fleet,
@@ -147,8 +153,43 @@ def release(
)
self._logger.info("Terminated EC2 Fleet %s instances: %s", fleet_id, instance_ids)
- if decision.is_full_return and decision.has_fleet_record:
- self._logger.info("EC2 Fleet %s capacity is zero, deleting fleet", fleet_id)
+ # Determine whether all fleet instances have been returned.
+ # decision.is_full_return is based on the weighted capacity sum, so it
+ # correctly handles weighted fleets. The secondary instance-count check
+ # below acts as a defensive net for races where the fleet might have been
+ # partially refilled between our capacity-reduce call and here.
+ should_delete_fleet = decision.is_full_return
+ if not should_delete_fleet and decision.has_fleet_record:
+ # Weighted-capacity fallback: the fleet may be logically empty even
+ # if the capacity arithmetic suggests otherwise.
+ should_delete_fleet = self._fleet_has_no_remaining_instances(
+ fleet_id, set(instance_ids)
+ )
+ if should_delete_fleet:
+ self._logger.info(
+ "EC2 Fleet %s has no remaining active instances "
+ "(weighted-capacity case); treating as full return",
+ fleet_id,
+ )
+ # Force capacity to 0 before deletion to prevent AWS from
+ # launching replacement instances during the delete window.
+ if decision.requires_capacity_reduction:
+ try:
+ self._retry(
+ self._aws_client.ec2_client.modify_fleet,
+ operation_type="critical",
+ FleetId=fleet_id,
+ TargetCapacitySpecification={"TotalTargetCapacity": 0},
+ )
+ except Exception as exc:
+ self._logger.warning(
+ "Failed to zero EC2 Fleet %s capacity before deletion: %s",
+ fleet_id,
+ exc,
+ )
+
+ if should_delete_fleet and decision.has_fleet_record:
+ self._logger.info("EC2 Fleet %s is empty, deleting fleet", fleet_id)
if decision.requires_capacity_reduction:
# maintain fleet — use _delete_fleet (TerminateInstances=True)
self._delete_fleet(fleet_id)
@@ -162,7 +203,26 @@ def release(
)
self._maybe_cleanup_launch_template(fleet_details)
elif not decision.has_fleet_record:
- # instant fleet — already deleted by AWS; only clean up the launch template.
+ # instant fleet — AWS may or may not have auto-deleted the fleet record
+ # (on-demand instant fleets in particular can remain in "active" state
+ # indefinitely). Always attempt an explicit delete so the fleet is
+ # removed promptly; swallow any error in case it was already gone.
+ try:
+ self._retry(
+ self._aws_client.ec2_client.delete_fleets,
+ operation_type="critical",
+ FleetIds=[fleet_id],
+ TerminateInstances=True,
+ )
+ self._logger.info(
+ "Deleted instant EC2 Fleet %s (instances already terminated)", fleet_id
+ )
+ except Exception as exc:
+ self._logger.warning(
+ "Could not delete instant EC2 Fleet %s (may already be gone): %s",
+ fleet_id,
+ exc,
+ )
self._maybe_cleanup_launch_template(fleet_details)
else:
self._delete_fleet(fleet_id)
@@ -230,6 +290,122 @@ def find_fleet_for_instance(self, instance_id: str) -> Optional[str]:
# Private helpers
# ------------------------------------------------------------------
+ def _fleet_has_no_remaining_instances(self, fleet_id: str, excluded_ids: set[str]) -> bool:
+ """Return True when the EC2 Fleet has no active instances outside *excluded_ids*.
+
+ Used as a secondary full-return detector for weighted fleets where the
+ capacity arithmetic alone is insufficient: a single instance with
+ WeightedCapacity > 1 can satisfy a TotalTargetCapacity > 1, but the
+ capacity counter only decrements by 1 per instance detached.
+
+ Args:
+ fleet_id: EC2 Fleet ID to inspect.
+ excluded_ids: Instance IDs that have already been submitted for
+ termination and should be treated as gone.
+
+ Returns:
+ True when no active instances remain, False when any do (or on error).
+ """
+ try:
+ active = self._collect_with_next_token(
+ self._aws_client.ec2_client.describe_fleet_instances,
+ "ActiveInstances",
+ FleetId=fleet_id,
+ )
+ remaining = [inst for inst in active if inst.get("InstanceId") not in excluded_ids]
+ return len(remaining) == 0
+ except Exception as exc:
+ self._logger.warning(
+ "Could not verify remaining instances for EC2 Fleet %s: %s — "
+ "assuming non-empty (safe default)",
+ fleet_id,
+ exc,
+ )
+ # Safe default: assume the fleet still has instances rather than
+ # accidentally deleting a fleet that has active instances.
+ return False
+
+ def _sum_weighted_capacity(
+ self,
+ fleet_id: str,
+ fleet_details: dict[str, Any],
+ instance_ids: list[str],
+ ) -> int:
+ """Return the total WeightedCapacity consumed by *instance_ids* in this fleet.
+
+ Queries ``describe_fleet_instances`` to find the InstanceType for each
+ active instance, then looks up the WeightedCapacity for that type in
+ ``LaunchTemplateConfigs[].Overrides[].WeightedCapacity``.
+
+ Instances that are not present in ``ActiveInstances`` (already terminated
+ or in the middle of a race) default to a weight of 1 so that the capacity
+ arithmetic errs on the side of *too small* a decrement rather than leaving
+ orphaned capacity that AWS would refill.
+
+ Args:
+ fleet_id: EC2 Fleet ID.
+ fleet_details: Pre-fetched DescribeFleets entry for this fleet.
+ instance_ids: The specific instance IDs being returned.
+
+ Returns:
+ Sum of weighted capacity units to subtract from TotalTargetCapacity.
+ """
+ # Build a map of instance_type → WeightedCapacity from the fleet launch spec.
+ weight_by_type: dict[str, int] = {}
+ for lt_config in fleet_details.get("LaunchTemplateConfigs", []):
+ for override in lt_config.get("Overrides", []):
+ itype = override.get("InstanceType")
+ raw_weight = override.get("WeightedCapacity")
+ if itype and raw_weight is not None:
+ try:
+ weight_by_type[itype] = int(raw_weight)
+ except (TypeError, ValueError):
+ pass
+
+ if not weight_by_type:
+ # Fleet has no WeightedCapacity overrides at all — each instance counts as 1.
+ # Skip the describe_fleet_instances API call entirely; result is just the count.
+ self._logger.debug(
+ "EC2 Fleet %s has no WeightedCapacity overrides; "
+ "using instance count %d as capacity decrement",
+ fleet_id,
+ len(instance_ids),
+ )
+ return max(1, len(instance_ids))
+
+ # Fetch the current active instances so we know each instance's type.
+ instance_type_by_id: dict[str, str] = {}
+ try:
+ active = self._collect_with_next_token(
+ self._aws_client.ec2_client.describe_fleet_instances,
+ "ActiveInstances",
+ FleetId=fleet_id,
+ )
+ for item in active:
+ iid = item.get("InstanceId")
+ itype = item.get("InstanceType")
+ if iid and itype:
+ instance_type_by_id[iid] = itype
+ except Exception as exc:
+ self._logger.warning(
+ "Could not fetch active instances for EC2 Fleet %s to compute "
+ "weighted capacity; defaulting all instance weights to 1: %s",
+ fleet_id,
+ exc,
+ )
+
+ total = 0
+ for iid in instance_ids:
+ itype = instance_type_by_id.get(iid)
+ if itype and itype in weight_by_type:
+ total += weight_by_type[itype]
+ else:
+ # Instance not found in ActiveInstances (already terminated / race),
+ # or instance type has no explicit weight → default to 1.
+ total += 1
+
+ return max(1, total)
+
def _delete_fleet(self, fleet_id: str) -> None:
"""Delete an EC2 Fleet, terminating its instances."""
try:
diff --git a/src/orb/providers/aws/infrastructure/handlers/fleet_release_policy.py b/src/orb/providers/aws/infrastructure/handlers/fleet_release_policy.py
index 12fd147e1..9d8718294 100644
--- a/src/orb/providers/aws/infrastructure/handlers/fleet_release_policy.py
+++ b/src/orb/providers/aws/infrastructure/handlers/fleet_release_policy.py
@@ -17,7 +17,7 @@ class FleetReleaseDecision:
def compute_fleet_release_decision(
fleet_type: str,
current_capacity: int,
- instances_to_return: int,
+ weighted_capacity_to_return: int,
) -> FleetReleaseDecision:
"""Compute what actions are required when returning instances from a fleet.
@@ -25,7 +25,10 @@ def compute_fleet_release_decision(
fleet_type: Raw fleet type string (e.g. "maintain", "request", "instant").
Accepts enum values — normalised to lowercase string internally.
current_capacity: Current TotalTargetCapacity / TargetCapacity of the fleet.
- instances_to_return: Number of instances being returned in this call.
+ weighted_capacity_to_return: Sum of WeightedCapacity for all instances being
+ returned. For unweighted fleets this equals the instance count.
+ Using the weighted sum prevents a race window where AWS refills
+ capacity units that were not decremented by the right amount.
Returns:
A FleetReleaseDecision describing which actions the caller must take.
@@ -38,7 +41,7 @@ def compute_fleet_release_decision(
raw = fleet_type.value # type: ignore[union-attr]
fleet_type_lower = str(raw).lower()
- remaining = max(0, current_capacity - instances_to_return)
+ remaining = max(0, current_capacity - weighted_capacity_to_return)
is_full = remaining == 0
if fleet_type_lower == "maintain":
diff --git a/src/orb/providers/aws/infrastructure/handlers/run_instances/handler.py b/src/orb/providers/aws/infrastructure/handlers/run_instances/handler.py
index 14ae00f01..578005a18 100644
--- a/src/orb/providers/aws/infrastructure/handlers/run_instances/handler.py
+++ b/src/orb/providers/aws/infrastructure/handlers/run_instances/handler.py
@@ -34,6 +34,7 @@
from orb.domain.base.dependency_injection import injectable
from orb.domain.base.ports import ErrorHandlingPort, LoggingPort
from orb.domain.base.ports.configuration_port import ConfigurationPort
+from orb.domain.base.provider_fulfilment import CheckHostsStatusResult, ProviderFulfilment
from orb.domain.request.aggregate import Request
from orb.domain.template.template_aggregate import Template
from orb.infrastructure.adapters.ports.request_adapter_port import RequestAdapterPort
@@ -410,8 +411,15 @@ def _create_run_instances_params_legacy(
return params
- def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
- """Check the status of instances created by RunInstances."""
+ def check_hosts_status(self, request: Request) -> CheckHostsStatusResult:
+ """Check the status of instances created by RunInstances.
+
+ Fulfilment semantics:
+ RunInstances is a synchronous API — each instance equals 1 capacity
+ unit. The request is ``fulfilled`` when running_count >= requested_count
+ and failed_count == 0. If the describe response is empty we treat
+ that as still in_progress so the next poll can confirm.
+ """
try:
# Get instance IDs from provider_data (preferred) or fallback to metadata for backward compatibility
instance_ids = (
@@ -433,7 +441,17 @@ def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
"No instance IDs or resource IDs found in request %s",
request.request_id,
)
- return []
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="No instance IDs yet — waiting for provisioning",
+ target_units=request.requested_count,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
# Get resource ID from provider_data (preferred) or domain field or fallback to metadata
resource_id = (
@@ -459,17 +477,85 @@ def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
provider_api="RunInstances",
)
- return self._format_instance_data(
+ instances = self._format_instance_data(
instance_details, resource_id, self._resolve_provider_api(request)
)
+ fulfilment = self._compute_run_instances_fulfilment(instances, request.requested_count)
+ return CheckHostsStatusResult(instances=instances, fulfilment=fulfilment)
except Exception as e:
self._logger.error("Unexpected error checking RunInstances status: %s", str(e))
raise AWSInfrastructureError(f"Failed to check RunInstances status: {e!s}")
+ def _compute_run_instances_fulfilment(
+ self, instances: list[dict[str, Any]], requested_count: int
+ ) -> ProviderFulfilment:
+ """Compute ProviderFulfilment for a RunInstances-style request.
+
+ RunInstances is sync/simple: 1 instance == 1 capacity unit.
+ The request is fulfilled when running_count >= requested_count and
+ no failed instances exist.
+ """
+ running_count = sum(1 for i in instances if i.get("status") == "running")
+ pending_count = sum(1 for i in instances if i.get("status") in ("pending", "starting"))
+ failed_count = sum(
+ 1 for i in instances if i.get("status") in ("failed", "error", "shutting-down")
+ )
+
+ if running_count >= requested_count and failed_count == 0:
+ return ProviderFulfilment(
+ state="fulfilled",
+ message=f"All {running_count} instance(s) running",
+ target_units=requested_count,
+ fulfilled_units=running_count,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif pending_count > 0:
+ return ProviderFulfilment(
+ state="in_progress",
+ message=f"{running_count}/{requested_count} running, {pending_count} pending",
+ target_units=requested_count,
+ fulfilled_units=running_count,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif failed_count == len(instances) and len(instances) > 0:
+ return ProviderFulfilment(
+ state="failed",
+ message=f"All {failed_count} instance(s) failed",
+ target_units=requested_count,
+ fulfilled_units=0,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif running_count > 0:
+ return ProviderFulfilment(
+ state="partial",
+ message=f"{running_count}/{requested_count} instance(s) running",
+ target_units=requested_count,
+ fulfilled_units=running_count,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ else:
+ return ProviderFulfilment(
+ state="in_progress",
+ message="Instances starting",
+ target_units=requested_count,
+ fulfilled_units=0,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+
def _find_instances_by_resource_ids(
self, request: Request, resource_ids: list[str]
- ) -> list[dict[str, Any]]:
+ ) -> CheckHostsStatusResult:
"""Find instances using resource IDs (reservation IDs for RunInstances)."""
try:
all_instances: list[dict[str, Any]] = []
@@ -522,7 +608,10 @@ def _find_instances_by_resource_ids(
resource_ids,
)
- return all_instances
+ fulfilment = self._compute_run_instances_fulfilment(
+ all_instances, request.requested_count
+ )
+ return CheckHostsStatusResult(instances=all_instances, fulfilment=fulfilment)
except Exception as e:
self._logger.error(
@@ -534,7 +623,7 @@ def _find_instances_by_resource_ids(
def _find_instances_by_tags_fallback(
self, request: Request, resource_ids: list[str]
- ) -> list[dict[str, Any]]:
+ ) -> CheckHostsStatusResult:
"""Fallback method to find instances by tags when reservation-id filter is not supported."""
try:
response = self.aws_client.ec2_client.describe_instances()
@@ -565,11 +654,24 @@ def _find_instances_by_tags_fallback(
)
)
- return formatted_instances
+ fulfilment = self._compute_run_instances_fulfilment(
+ formatted_instances, request.requested_count
+ )
+ return CheckHostsStatusResult(instances=formatted_instances, fulfilment=fulfilment)
except Exception as e:
self._logger.error("FALLBACK: Fallback method failed to find instances: %s", e)
- return []
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="Instance lookup failed — will retry",
+ target_units=request.requested_count,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
def release_hosts(
self,
@@ -677,7 +779,7 @@ def get_example_templates(cls) -> list[Template]:
description="On-demand instances using RunInstances API",
provider_api="RunInstances",
machine_types={"t3.medium": 2},
- max_instances=5,
+ max_instances=100,
price_type="ondemand",
subnet_ids=[],
security_group_ids=[],
@@ -689,7 +791,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot instances using RunInstances API",
provider_api="RunInstances",
machine_types={"t3.medium": 2},
- max_instances=10,
+ max_instances=100,
price_type="spot",
max_price=0.10,
subnet_ids=[],
diff --git a/src/orb/providers/aws/infrastructure/handlers/shared/fleet_fulfilment.py b/src/orb/providers/aws/infrastructure/handlers/shared/fleet_fulfilment.py
new file mode 100644
index 000000000..15bdad127
--- /dev/null
+++ b/src/orb/providers/aws/infrastructure/handlers/shared/fleet_fulfilment.py
@@ -0,0 +1,74 @@
+"""Shared capacity-based fleet fulfilment computation.
+
+Both EC2 Fleet (Maintain/Request types) and Spot Fleet share identical
+fulfilment semantics: FulfilledCapacity >= TargetCapacity AND no pending
+or failed instances → fulfilled. The only difference is the label used
+in human-readable messages.
+"""
+
+from __future__ import annotations
+
+from typing import Optional
+
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
+
+
+def compute_capacity_based_fulfilment(
+ target_capacity: Optional[int],
+ fulfilled_capacity: float,
+ running_count: int,
+ pending_count: int,
+ failed_count: int,
+ provider_label: str,
+) -> ProviderFulfilment:
+ """Compute ProviderFulfilment for a capacity-unit based fleet.
+
+ Used by EC2 Fleet (Maintain/Request) and Spot Fleet handlers.
+
+ Args:
+ target_capacity: The fleet's TargetCapacity, or None if unknown.
+ fulfilled_capacity: The fleet's FulfilledCapacity as reported by AWS.
+ running_count: Number of instances whose status is "running".
+ pending_count: Number of instances whose status is "pending" or "starting".
+ failed_count: Number of instances whose status is "failed" or "error".
+ provider_label: Label used in messages, e.g. "Fleet" or "Spot Fleet".
+ """
+ target_units = target_capacity if target_capacity is not None else int(fulfilled_capacity)
+ fleet_fully_fulfilled = target_capacity is not None and fulfilled_capacity >= target_capacity
+
+ if fleet_fully_fulfilled and pending_count == 0 and failed_count == 0:
+ return ProviderFulfilment(
+ state="fulfilled",
+ message=(
+ f"{provider_label} fulfilled: {running_count} instance(s) running "
+ f"({fulfilled_capacity}/{target_capacity} capacity units)"
+ ),
+ target_units=target_units,
+ fulfilled_units=int(fulfilled_capacity),
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ elif failed_count > 0 and running_count == 0 and pending_count == 0:
+ return ProviderFulfilment(
+ state="failed",
+ message=f"{provider_label} failed: {failed_count} instance(s) failed",
+ target_units=target_units,
+ fulfilled_units=int(fulfilled_capacity),
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
+ else:
+ return ProviderFulfilment(
+ state="in_progress",
+ message=(
+ f"{provider_label}: {running_count} running, {pending_count} pending "
+ f"({fulfilled_capacity}/{target_units} capacity units)"
+ ),
+ target_units=target_units,
+ fulfilled_units=int(fulfilled_capacity),
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ )
diff --git a/src/orb/providers/aws/infrastructure/handlers/shared/fleet_grouping_mixin.py b/src/orb/providers/aws/infrastructure/handlers/shared/fleet_grouping_mixin.py
index 804ec7285..11b937459 100644
--- a/src/orb/providers/aws/infrastructure/handlers/shared/fleet_grouping_mixin.py
+++ b/src/orb/providers/aws/infrastructure/handlers/shared/fleet_grouping_mixin.py
@@ -119,11 +119,14 @@ def _group_instances_from_mapping(
self._collect_groups_from_instances(
instances_needing_lookup, groups, group_ids_to_fetch
)
- # Fetch details only for groups discovered via AWS lookup — groups resolved
- # from the mapping are left with details=None so the release manager fetches
- # them on demand (avoiding unnecessary describe calls on the return path).
- if group_ids_to_fetch:
- self._fetch_and_attach_group_details(group_ids_to_fetch, groups)
+
+ # Fetch details for all known group IDs regardless of how they were resolved.
+ # When all instances come from resource_mapping, instances_needing_lookup is empty
+ # but group_ids_to_fetch is still populated from the mapping loop above. Without
+ # this fetch, group details remain None and release_manager falls back to a raw
+ # describe that reads MinSize directly from AWS, creating a double-decrement risk.
+ if group_ids_to_fetch:
+ self._fetch_and_attach_group_details(group_ids_to_fetch, groups)
# Log performance metrics and optimization results
self._log_grouping_summary(
diff --git a/src/orb/providers/aws/infrastructure/handlers/spot_fleet/config_builder.py b/src/orb/providers/aws/infrastructure/handlers/spot_fleet/config_builder.py
index f8ebc4910..7dd083ae5 100644
--- a/src/orb/providers/aws/infrastructure/handlers/spot_fleet/config_builder.py
+++ b/src/orb/providers/aws/infrastructure/handlers/spot_fleet/config_builder.py
@@ -268,7 +268,7 @@ def _build_legacy(
if price_type in ("ondemand", "heterogeneous") or on_demand_capacity > 0:
fleet_config["OnDemandTargetCapacity"] = on_demand_capacity
- if template.fleet_type == AWSFleetType.MAINTAIN.value:
+ if template.fleet_type == AWSFleetType.MAINTAIN:
fleet_config["ReplaceUnhealthyInstances"] = True
fleet_config["TerminateInstancesWithExpiration"] = True
diff --git a/src/orb/providers/aws/infrastructure/handlers/spot_fleet/handler.py b/src/orb/providers/aws/infrastructure/handlers/spot_fleet/handler.py
index 35565fae7..b58601f5b 100644
--- a/src/orb/providers/aws/infrastructure/handlers/spot_fleet/handler.py
+++ b/src/orb/providers/aws/infrastructure/handlers/spot_fleet/handler.py
@@ -31,6 +31,7 @@
from orb.domain.base.dependency_injection import injectable
from orb.domain.base.ports import LoggingPort
from orb.domain.base.ports.configuration_port import ConfigurationPort
+from orb.domain.base.provider_fulfilment import CheckHostsStatusResult, ProviderFulfilment
from orb.domain.request.aggregate import Request
from orb.domain.template.template_aggregate import Template
from orb.infrastructure.adapters.ports.request_adapter_port import RequestAdapterPort
@@ -44,6 +45,9 @@
from orb.providers.aws.infrastructure.aws_client import AWSClient
from orb.providers.aws.infrastructure.handlers.base_handler import AWSHandler
from orb.providers.aws.infrastructure.handlers.shared.base_context_mixin import BaseContextMixin
+from orb.providers.aws.infrastructure.handlers.shared.fleet_fulfilment import (
+ compute_capacity_based_fulfilment,
+)
from orb.providers.aws.infrastructure.handlers.shared.fleet_grouping_mixin import FleetGroupingMixin
from orb.providers.aws.infrastructure.handlers.spot_fleet.config_builder import (
SpotFleetConfigBuilder,
@@ -241,44 +245,91 @@ def _resolve_fleet_role(self, aws_template: AWSTemplate) -> AWSTemplate:
aws_template = aws_template.model_copy(update={"fleet_role": resolved})
return aws_template
- def check_hosts_status(self, request: Request) -> list[dict[str, Any]]:
- """Check the status of instances across all spot fleets in the request."""
+ def check_hosts_status(self, request: Request) -> CheckHostsStatusResult:
+ """Check the status of instances across all spot fleets in the request.
+
+ Fulfilment semantics (same as EC2Fleet Maintain/Request):
+ FulfilledCapacity >= TargetCapacity AND pending_count == 0
+ AND failed_count == 0 → fulfilled.
+ """
try:
if not request.resource_ids:
self._logger.info("No Spot Fleet Request IDs found in request")
- return []
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="No Spot Fleet IDs yet — waiting for provisioning",
+ target_units=request.requested_count,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
- all_instances = []
+ all_instances: list[dict[str, Any]] = []
+ fleet_results: list[CheckHostsStatusResult] = []
request_id = str(request.request_id)
- # Process all fleet IDs instead of just the first one
for fleet_id in request.resource_ids:
try:
- fleet_instances = self._get_spot_fleet_instances(
- fleet_id, request_id=request_id
+ result = self._get_spot_fleet_status(
+ fleet_id,
+ request_id=request_id,
+ requested_count=request.requested_count,
)
- if fleet_instances:
- formatted_instances = self._format_instance_data(
- fleet_instances, fleet_id, self._resolve_provider_api(request)
- )
- all_instances.extend(formatted_instances)
+ all_instances.extend(result.instances)
+ fleet_results.append(result)
except Exception as e:
self._logger.error("Failed to get instances for spot fleet %s: %s", fleet_id, e)
continue
- return all_instances
+ if not fleet_results:
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="No Spot Fleet status available — will retry",
+ ),
+ )
+
+ if len(fleet_results) == 1:
+ return CheckHostsStatusResult(
+ instances=all_instances,
+ fulfilment=fleet_results[0].fulfilment,
+ )
+
+ states = [r.fulfilment.state for r in fleet_results]
+ if all(s == "fulfilled" for s in states):
+ combined_state = "fulfilled"
+ combined_msg = f"All {len(fleet_results)} spot fleets fulfilled"
+ elif any(s == "failed" for s in states):
+ combined_state = "failed"
+ combined_msg = "One or more spot fleets failed"
+ elif any(s == "partial" for s in states):
+ combined_state = "partial"
+ combined_msg = "One or more spot fleets partially fulfilled"
+ else:
+ combined_state = "in_progress"
+ combined_msg = "Waiting for spot fleet(s) to fulfil"
+
+ return CheckHostsStatusResult(
+ instances=all_instances,
+ fulfilment=ProviderFulfilment(state=combined_state, message=combined_msg),
+ )
except Exception as e:
self._logger.error("Unexpected error checking Spot Fleet status: %s", str(e))
raise AWSInfrastructureError(f"Failed to check Spot Fleet status: {e!s}")
- def _get_spot_fleet_instances(
+ def _get_spot_fleet_status(
self,
fleet_id: str,
- request_id: str = None, # type: ignore[assignment]
- ) -> list[dict[str, Any]]:
- """Get instances for a specific spot fleet."""
- # Get fleet information
+ request_id: str = "",
+ requested_count: int = 1,
+ ) -> CheckHostsStatusResult:
+ """Get status + fulfilment for a specific Spot Fleet request."""
+ # Get fleet config (includes TargetCapacity and FulfilledCapacity)
fleet_list = self._retry_with_backoff(
lambda: self._paginate(
self.aws_client.ec2_client.describe_spot_fleet_requests,
@@ -289,7 +340,23 @@ def _get_spot_fleet_instances(
if not fleet_list:
self._logger.warning("Spot Fleet Request %s not found", fleet_id)
- return []
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message=f"Spot Fleet {fleet_id} not yet visible — waiting",
+ target_units=requested_count,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
+
+ fleet_config_entry = fleet_list[0]
+ fleet_cfg = fleet_config_entry.get("SpotFleetRequestConfig") or {}
+ target_capacity: Optional[int] = fleet_cfg.get("TargetCapacity")
+ fulfilled_capacity: float = fleet_cfg.get("FulfilledCapacity") or 0.0
+ target_units = target_capacity if target_capacity is not None else requested_count
# Get active instances
active_instances = self._retry_with_backoff(
@@ -301,13 +368,84 @@ def _get_spot_fleet_instances(
)
if not active_instances:
- return []
+ # Fleet submitted but no instances yet — check if capacity has been allocated
+ fleet_fully_fulfilled = (
+ target_capacity is not None and fulfilled_capacity >= target_capacity
+ )
+ if fleet_fully_fulfilled:
+ # Capacity allocated but instances not visible yet
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message=f"Spot Fleet capacity allocated ({fulfilled_capacity}/{target_capacity}), instances starting",
+ target_units=target_units,
+ fulfilled_units=int(fulfilled_capacity),
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
+ return CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message=f"Spot Fleet waiting for instances ({fulfilled_capacity}/{target_units} capacity units)",
+ target_units=target_units,
+ fulfilled_units=int(fulfilled_capacity),
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
instance_ids = [instance["InstanceId"] for instance in active_instances]
- return self._get_instance_details(
+ instance_details = self._get_instance_details(
instance_ids, request_id=request_id, resource_id=fleet_id, provider_api="SpotFleet"
)
+ fulfilment = self._compute_spot_fleet_fulfilment(
+ instances=instance_details,
+ target_capacity=target_capacity,
+ fulfilled_capacity=fulfilled_capacity,
+ requested_count=requested_count,
+ )
+ return CheckHostsStatusResult(instances=instance_details, fulfilment=fulfilment)
+
+ def _compute_spot_fleet_fulfilment(
+ self,
+ instances: list[dict[str, Any]],
+ target_capacity: Optional[int],
+ fulfilled_capacity: float,
+ requested_count: int,
+ ) -> ProviderFulfilment:
+ """Compute ProviderFulfilment for a Spot Fleet request.
+
+ Same semantics as EC2Fleet Maintain/Request:
+ FulfilledCapacity >= TargetCapacity AND pending_count == 0
+ AND failed_count == 0 → fulfilled.
+ """
+ running_count = sum(1 for i in instances if i.get("status") == "running")
+ pending_count = sum(1 for i in instances if i.get("status") in ("pending", "starting"))
+ failed_count = sum(1 for i in instances if i.get("status") in ("failed", "error"))
+ return compute_capacity_based_fulfilment(
+ target_capacity=target_capacity,
+ fulfilled_capacity=fulfilled_capacity,
+ running_count=running_count,
+ pending_count=pending_count,
+ failed_count=failed_count,
+ provider_label="Spot Fleet",
+ )
+
+ def _get_spot_fleet_instances(
+ self,
+ fleet_id: str,
+ request_id: str = "", # type: ignore[assignment]
+ ) -> list[dict[str, Any]]:
+ """Get raw instance list for a specific spot fleet (legacy helper — use _get_spot_fleet_status for new code)."""
+ result = self._get_spot_fleet_status(fleet_id, request_id=request_id)
+ return result.instances
+
def _resolve_provider_api(
self, request: Request, aws_template: Optional[AWSTemplate] = None
) -> str:
@@ -535,7 +673,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot Fleet request with lowest price allocation",
provider_api="SpotFleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=20,
+ max_instances=100,
price_type="spot",
allocation_strategy="lowestPrice",
fleet_type="request",
@@ -550,7 +688,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot Fleet request with diversified allocation",
provider_api="SpotFleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=25,
+ max_instances=100,
price_type="spot",
allocation_strategy="diversified",
fleet_type="request",
@@ -565,7 +703,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot Fleet request with capacity optimized allocation",
provider_api="SpotFleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=30,
+ max_instances=100,
price_type="spot",
allocation_strategy="capacityOptimized",
fleet_type="request",
@@ -581,7 +719,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot Fleet maintain with lowest price allocation",
provider_api="SpotFleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=15,
+ max_instances=100,
price_type="spot",
allocation_strategy="lowestPrice",
fleet_type="maintain",
@@ -596,7 +734,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot Fleet maintain with diversified allocation",
provider_api="SpotFleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=20,
+ max_instances=100,
price_type="spot",
allocation_strategy="diversified",
fleet_type="maintain",
@@ -611,7 +749,7 @@ def get_example_templates(cls) -> list[Template]:
description="Spot Fleet maintain with capacity optimized allocation",
provider_api="SpotFleet",
machine_types={"t3.medium": 2, "t3.large": 2, "t3.xlarge": 4},
- max_instances=25,
+ max_instances=100,
price_type="spot",
allocation_strategy="capacityOptimized",
fleet_type="maintain",
diff --git a/src/orb/providers/aws/infrastructure/handlers/spot_fleet/release_manager.py b/src/orb/providers/aws/infrastructure/handlers/spot_fleet/release_manager.py
index 2c9d5271b..a601fdb04 100644
--- a/src/orb/providers/aws/infrastructure/handlers/spot_fleet/release_manager.py
+++ b/src/orb/providers/aws/infrastructure/handlers/spot_fleet/release_manager.py
@@ -71,22 +71,28 @@ def release(
on_demand_capacity = int(fleet_config.get("OnDemandTargetCapacity", 0) or 0)
if instance_ids:
+ weighted_capacity_to_return = self._sum_weighted_capacity(
+ fleet_id, fleet_config, instance_ids
+ )
+
decision = compute_fleet_release_decision(
fleet_type=fleet_type,
current_capacity=target_capacity,
- instances_to_return=len(instance_ids),
+ weighted_capacity_to_return=weighted_capacity_to_return,
)
if decision.requires_capacity_reduction:
- new_target_capacity = max(0, target_capacity - len(instance_ids))
+ new_target_capacity = max(0, target_capacity - weighted_capacity_to_return)
new_on_demand_capacity = min(on_demand_capacity, new_target_capacity)
self._logger.info(
- "Reducing %s Spot Fleet %s capacity from %s to %s before terminating instances",
+ "Reducing %s Spot Fleet %s capacity from %s to %s "
+ "(weighted_capacity_to_return=%s) before terminating instances",
fleet_type,
fleet_id,
target_capacity,
new_target_capacity,
+ weighted_capacity_to_return,
)
self._retry(
@@ -102,8 +108,40 @@ def release(
)
self._logger.info("Terminated Spot Fleet %s instances: %s", fleet_id, instance_ids)
- if decision.is_full_return and decision.has_fleet_record:
- self._logger.info("Spot Fleet %s capacity is zero, cancelling fleet", fleet_id)
+ # Determine whether all fleet instances have been returned.
+ # decision.is_full_return is based on the weighted capacity sum, so it
+ # correctly handles weighted fleets. The secondary instance-count check
+ # below acts as a defensive net for races.
+ should_cancel_fleet = decision.is_full_return
+ if not should_cancel_fleet and decision.has_fleet_record:
+ should_cancel_fleet = self._fleet_has_no_remaining_instances(
+ fleet_id, set(instance_ids)
+ )
+ if should_cancel_fleet:
+ self._logger.info(
+ "Spot Fleet %s has no remaining active instances "
+ "(weighted-capacity case); treating as full return",
+ fleet_id,
+ )
+ # Zero the capacity to prevent replacement before cancellation.
+ if decision.requires_capacity_reduction:
+ try:
+ self._retry(
+ self._aws_client.ec2_client.modify_spot_fleet_request,
+ operation_type="critical",
+ SpotFleetRequestId=fleet_id,
+ TargetCapacity=0,
+ OnDemandTargetCapacity=0,
+ )
+ except Exception as exc:
+ self._logger.warning(
+ "Failed to zero Spot Fleet %s capacity before cancellation: %s",
+ fleet_id,
+ exc,
+ )
+
+ if should_cancel_fleet and decision.has_fleet_record:
+ self._logger.info("Spot Fleet %s is empty, cancelling fleet", fleet_id)
self._retry(
self._aws_client.ec2_client.cancel_spot_fleet_requests,
operation_type="critical",
@@ -177,6 +215,134 @@ def find_fleet_for_instance(self, instance_id: str) -> Optional[str]:
# Private helpers
# ------------------------------------------------------------------
+ def _fleet_has_no_remaining_instances(self, fleet_id: str, excluded_ids: set[str]) -> bool:
+ """Return True when the Spot Fleet has no active instances outside *excluded_ids*.
+
+ Used as a secondary full-return detector for weighted fleets where the
+ capacity arithmetic alone is insufficient.
+
+ Args:
+ fleet_id: Spot Fleet request ID to inspect.
+ excluded_ids: Instance IDs that have already been submitted for
+ termination and should be treated as gone.
+
+ Returns:
+ True when no active instances remain, False when any do (or on error).
+ """
+ try:
+ resp = self._retry(
+ self._aws_client.ec2_client.describe_spot_fleet_instances,
+ operation_type="read_only",
+ SpotFleetRequestId=fleet_id,
+ )
+ active = resp.get("ActiveInstances", [])
+ remaining = [inst for inst in active if inst.get("InstanceId") not in excluded_ids]
+ return len(remaining) == 0
+ except Exception as exc:
+ self._logger.warning(
+ "Could not verify remaining instances for Spot Fleet %s: %s — "
+ "assuming non-empty (safe default)",
+ fleet_id,
+ exc,
+ )
+ return False
+
+ def _sum_weighted_capacity(
+ self,
+ fleet_id: str,
+ fleet_config: dict[str, Any],
+ instance_ids: list[str],
+ ) -> int:
+ """Return the total WeightedCapacity consumed by *instance_ids* in this Spot Fleet.
+
+ Queries ``describe_spot_fleet_instances`` (which includes ``WeightedCapacity``
+ directly on each ``ActiveInstances`` entry) to resolve each returning instance's
+ weight. Falls back to the ``LaunchSpecifications`` / ``LaunchTemplateConfigs``
+ weight-by-type map when an instance is absent from ``ActiveInstances``
+ (already terminated or in a race). Instances with no resolvable weight
+ default to 1.
+
+ Args:
+ fleet_id: Spot Fleet request ID.
+ fleet_config: ``SpotFleetRequestConfig`` dict from the describe response.
+ instance_ids: The specific instance IDs being returned.
+
+ Returns:
+ Sum of weighted capacity units to subtract from TargetCapacity.
+ """
+ # Build a fallback map of instance_type → WeightedCapacity from the fleet spec.
+ weight_by_type: dict[str, int] = {}
+ for spec in fleet_config.get("LaunchSpecifications", []):
+ itype = spec.get("InstanceType")
+ raw_weight = spec.get("WeightedCapacity")
+ if itype and raw_weight is not None:
+ try:
+ weight_by_type[itype] = int(float(raw_weight))
+ except (TypeError, ValueError):
+ pass
+ for lt_config in fleet_config.get("LaunchTemplateConfigs", []):
+ for override in lt_config.get("Overrides", []):
+ itype = override.get("InstanceType")
+ raw_weight = override.get("WeightedCapacity")
+ if itype and raw_weight is not None:
+ try:
+ weight_by_type[itype] = int(float(raw_weight))
+ except (TypeError, ValueError):
+ pass
+
+ # Fetch the active instance list; the API returns WeightedCapacity per entry.
+ weight_by_instance_id: dict[str, int] = {}
+ instance_type_by_id: dict[str, str] = {}
+ try:
+ resp = self._retry(
+ self._aws_client.ec2_client.describe_spot_fleet_instances,
+ operation_type="read_only",
+ SpotFleetRequestId=fleet_id,
+ )
+ for item in resp.get("ActiveInstances", []):
+ iid = item.get("InstanceId")
+ itype = item.get("InstanceType")
+ raw_weight = item.get("WeightedCapacity")
+ if iid:
+ if itype:
+ instance_type_by_id[iid] = itype
+ if raw_weight is not None:
+ try:
+ weight_by_instance_id[iid] = int(float(raw_weight))
+ except (TypeError, ValueError):
+ pass
+ except Exception as exc:
+ self._logger.warning(
+ "Could not fetch active instances for Spot Fleet %s to compute "
+ "weighted capacity; defaulting all instance weights to 1: %s",
+ fleet_id,
+ exc,
+ )
+
+ total = 0
+ for iid in instance_ids:
+ # Prefer the per-instance weight from the live describe response.
+ if iid in weight_by_instance_id:
+ total += weight_by_instance_id[iid]
+ else:
+ # Fall back to the weight-by-type map from the fleet config.
+ itype = instance_type_by_id.get(iid)
+ if itype and itype in weight_by_type:
+ total += weight_by_type[itype]
+ else:
+ # Instance not found or type has no weight → default to 1.
+ total += 1
+
+ if not weight_by_type:
+ self._logger.debug(
+ "Spot Fleet %s has no WeightedCapacity overrides; "
+ "using instance count %d as capacity decrement",
+ fleet_id,
+ len(instance_ids),
+ )
+
+ return max(1, total)
+
def _retry(self, func: Any, operation_type: str = "standard", **kwargs: Any) -> Any:
"""Delegate to the injected retry function if available, else call directly."""
if self._retry_fn is not None:
diff --git a/src/orb/providers/aws/registration.py b/src/orb/providers/aws/registration.py
index f33060361..f64fb05c6 100644
--- a/src/orb/providers/aws/registration.py
+++ b/src/orb/providers/aws/registration.py
@@ -1,6 +1,8 @@
"""AWS Provider Registration - Register AWS provider with the provider registry."""
+import json
from contextlib import suppress
+from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional
# Use TYPE_CHECKING to avoid direct infrastructure import
@@ -9,9 +11,10 @@
from orb.providers.registry import ProviderRegistry
# Template extension imports for our new functionality
-from orb.domain.template.extensions import TemplateExtensionRegistry
from orb.domain.template.factory import TemplateFactory
+from orb.infrastructure.registry.template_extension_registry import TemplateExtensionRegistry
from orb.providers.aws.configuration.template_extension import AWSTemplateExtensionConfig
+from orb.providers.aws.domain.template.aws_template_dto_config import AWSTemplateDTOConfig
def create_aws_strategy(provider_config: Any) -> Any:
@@ -93,7 +96,11 @@ def create_aws_strategy(provider_config: Any) -> Any:
if strategy.aws_client is not None:
health_check = get_container().get(HealthCheckPort)
- register_aws_health_checks(health_check, strategy.aws_client)
+ _storage_strategy = "json"
+ if config_port is not None:
+ with suppress(Exception):
+ _storage_strategy = config_port.get_storage_strategy()
+ register_aws_health_checks(health_check, strategy.aws_client, _storage_strategy)
# Set provider name for identification
if hasattr(strategy, "name") and provider_name:
@@ -197,6 +204,26 @@ def create_aws_validator(provider_config: Any = None) -> Any:
raise RuntimeError(f"Failed to create AWS validator: {e!s}")
+def _load_aws_default_api() -> Optional[str]:
+ """Extract the default provider API from aws_defaults.json.
+
+ Returns the value at provider.provider_defaults.aws.template_defaults.provider_api,
+ or None if the file cannot be read or the key is absent.
+ """
+ try:
+ defaults_file = Path(__file__).parent / "config" / "aws_defaults.json"
+ data = json.loads(defaults_file.read_text())
+ return (
+ data.get("provider", {})
+ .get("provider_defaults", {})
+ .get("aws", {})
+ .get("template_defaults", {})
+ .get("provider_api")
+ )
+ except Exception:
+ return None
+
+
def register_aws_provider(
registry: "Optional[ProviderRegistry]" = None,
logger: "Optional[LoggingPort]" = None,
@@ -237,6 +264,7 @@ def register_aws_provider(
resolver_factory=create_aws_resolver,
validator_factory=create_aws_validator,
strategy_class=AWSProviderStrategy,
+ default_api=_load_aws_default_api(),
)
# Register AWS template store
@@ -358,12 +386,13 @@ def register_aws_extensions(logger: Optional["LoggingPort"] = None) -> None:
logger: Optional logger for registration messages
"""
try:
- # Register AWS template extension configuration
- TemplateExtensionRegistry.register_extension("aws", AWSTemplateExtensionConfig)
+ # Register AWS DTO config as the typed provider_config class for TemplateDTO
+ # serialisation. AWSTemplateDTOConfig covers the fields that were previously
+ # split between the top-level TemplateDTO and the opaque metadata dict.
+ TemplateExtensionRegistry.register_extension("aws", AWSTemplateDTOConfig)
if logger:
logger.debug("AWS template extensions registered successfully")
- # Remove print statement - should use structured logging
except Exception as e:
error_msg = f"Failed to register AWS template extensions: {e}"
@@ -413,6 +442,43 @@ def get_aws_extension_defaults() -> dict:
return default_config.to_template_defaults()
+def register_aws_auth_strategies(logger: "Optional[LoggingPort]" = None) -> None:
+ """Register AWS authentication strategies with the auth registry.
+
+ Registers the ``iam`` and ``cognito`` strategies so that ``AuthRegistry``
+ can resolve them without server.py importing provider-specific classes.
+
+ Args:
+ logger: Optional logger for registration messages
+ """
+ try:
+ from orb.infrastructure.auth.registry import get_auth_registry
+
+ registry = get_auth_registry()
+
+ if not registry.is_registered("iam"):
+ from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
+
+ registry.register_strategy("iam", IAMAuthStrategy)
+ if logger:
+ logger.debug("AWS IAM auth strategy registered")
+
+ if not registry.is_registered("cognito"):
+ from orb.providers.aws.auth.cognito_strategy import CognitoAuthStrategy
+
+ registry.register_strategy("cognito", CognitoAuthStrategy)
+ if logger:
+ logger.debug("AWS Cognito auth strategy registered")
+
+ except ImportError as e:
+ if logger:
+ logger.warning("AWS auth strategies not available: %s", e)
+ except Exception as e:
+ if logger:
+ logger.error("Failed to register AWS auth strategies: %s", e, exc_info=True)
+ raise
+
+
def initialize_aws_provider(
template_factory: Optional[TemplateFactory] = None,
logger: Optional["LoggingPort"] = None,
@@ -433,16 +499,33 @@ def initialize_aws_provider(
# Register AWS extensions
register_aws_extensions(logger)
+ # Register AWS authentication strategies
+ register_aws_auth_strategies(logger)
+
# Register AWS template factory if provided
if template_factory:
register_aws_template_factory(template_factory, logger)
# Register AWS CLI spec
- from orb.domain.base.ports.provider_cli_spec_port import CLISpecRegistry
+ from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
from orb.providers.aws.cli.aws_cli_spec import AWSCLISpec
CLISpecRegistry.register("aws", AWSCLISpec())
+ # Register AWS HostFactory field-mapping adapter
+ from orb.infrastructure.scheduler.hostfactory.field_mapping_registry import (
+ FieldMappingRegistry,
+ )
+ from orb.providers.aws.scheduler.hostfactory_field_mapping import AWSFieldMapping
+
+ FieldMappingRegistry.register("aws", AWSFieldMapping())
+
+ # Register AWS defaults loader
+ from orb.providers.aws.defaults_loader import AWSDefaultsLoader
+ from orb.providers.registry.defaults_loader_registry import DefaultsLoaderRegistry
+
+ DefaultsLoaderRegistry.register("aws", AWSDefaultsLoader())
+
if logger:
logger.info("AWS provider initialization completed successfully")
diff --git a/src/orb/providers/aws/scheduler/__init__.py b/src/orb/providers/aws/scheduler/__init__.py
new file mode 100644
index 000000000..15d36c40e
--- /dev/null
+++ b/src/orb/providers/aws/scheduler/__init__.py
@@ -0,0 +1 @@
+"""AWS scheduler adapters."""
diff --git a/src/orb/providers/aws/scheduler/hostfactory_field_mapping.py b/src/orb/providers/aws/scheduler/hostfactory_field_mapping.py
new file mode 100644
index 000000000..5d8b595e1
--- /dev/null
+++ b/src/orb/providers/aws/scheduler/hostfactory_field_mapping.py
@@ -0,0 +1,77 @@
+"""AWS implementation of FieldMappingPort for the HostFactory scheduler."""
+
+from orb.infrastructure.scheduler.hostfactory.field_mapping_port import FieldMappingPort
+
+
+class AWSFieldMapping:
+ """AWS-specific field-mapping adapter for the HostFactory scheduler.
+
+ Registers as ``"aws"`` in ``FieldMappingRegistry`` during provider
+ bootstrap (``providers/aws/registration.py``).
+ """
+
+ # AWS-specific HF field → internal field mappings.
+ # Generic mappings live in HostFactoryFieldMappings.MAPPINGS["generic"].
+ _PROVIDER_MAPPINGS: dict[str, str] = {
+ # AWS VPC network configuration
+ "subnetId": "subnet_ids",
+ "subnetIds": "subnet_ids",
+ "securityGroupIds": "security_group_ids",
+ # AWS instance type configurations
+ "vmTypesOnDemand": "machine_types_ondemand",
+ "vmTypesPriority": "machine_types_priority",
+ "abisInstanceRequirements": "abis_instance_requirements",
+ # AWS pricing configurations
+ "percentOnDemand": "percent_on_demand",
+ "allocationStrategyOnDemand": "allocation_strategy_on_demand",
+ # AWS fleet configurations
+ "fleetRole": "fleet_role",
+ "spotFleetRequestExpiry": "spot_fleet_request_expiry",
+ "poolsCount": "pools_count",
+ # AWS instance configuration
+ "instanceProfile": "instance_profile",
+ "launchTemplateId": "launch_template_id",
+ "userDataScript": "user_data",
+ }
+
+ def get_mappings(self) -> dict[str, str]:
+ """Return AWS-specific HF-field → internal-field name entries."""
+ return dict(self._PROVIDER_MAPPINGS)
+
+ def apply_defaults(self, mapped: dict) -> dict:
+ """Apply AWS-specific setdefault logic after field mapping.
+
+ When ``launch_template_id`` is set the launch template already encodes
+ network configuration, so ``subnet_ids`` and ``security_group_ids``
+ should not be defaulted to empty lists.
+ """
+ mapped.setdefault("max_instances", 1)
+ mapped.setdefault("price_type", "ondemand")
+ mapped.setdefault("allocation_strategy", "lowestPrice")
+ if not mapped.get("launch_template_id"):
+ mapped.setdefault("subnet_ids", [])
+ mapped.setdefault("security_group_ids", [])
+ mapped.setdefault("tags", {})
+ return mapped
+
+ def derive_attributes(self, machine_type: str | None) -> dict[str, list[str]] | None:
+ """Build the HF ``attributes`` object from an EC2 instance type string.
+
+ Returns ``None`` when *machine_type* is ``None`` or empty so callers
+ can fall back gracefully.
+ """
+ if not machine_type:
+ return None
+
+ from orb.providers.aws.utilities.ec2.instances import derive_cpu_ram_from_instance_type
+
+ ncpus, nram = derive_cpu_ram_from_instance_type(machine_type)
+ return {
+ "type": ["String", "X86_64"],
+ "ncpus": ["Numeric", str(ncpus)],
+ "nram": ["Numeric", str(nram)],
+ }
+
+
+# Verify the class satisfies the protocol at import time.
+_: FieldMappingPort = AWSFieldMapping() # type: ignore[assignment]
diff --git a/src/orb/providers/aws/strategy/aws_provider_adapter.py b/src/orb/providers/aws/strategy/aws_provider_adapter.py
index 43ea141bc..9cdbaf5ea 100644
--- a/src/orb/providers/aws/strategy/aws_provider_adapter.py
+++ b/src/orb/providers/aws/strategy/aws_provider_adapter.py
@@ -12,7 +12,6 @@
ProviderResourceTag,
ProviderResourceValidator,
ProviderStateMapper,
- ProviderType,
)
@@ -120,9 +119,9 @@ def __init__(self, logger: LoggingPort) -> None:
self._logger = logger
@property
- def provider_type(self) -> ProviderType:
+ def provider_type(self) -> str:
"""Get the provider type."""
- return ProviderType.AWS
+ return "aws"
@property
def state_mapper(self) -> ProviderStateMapper:
@@ -143,7 +142,7 @@ def create_resource_identifier(
raise ValueError(f"Invalid AWS {resource_type} identifier: {identifier}")
return ProviderResourceIdentifier(
- provider_type=ProviderType.AWS,
+ provider_type="aws",
resource_type=resource_type,
identifier=identifier,
region=region,
diff --git a/src/orb/providers/aws/strategy/aws_provider_strategy.py b/src/orb/providers/aws/strategy/aws_provider_strategy.py
index b1027db6b..4b9e0b010 100644
--- a/src/orb/providers/aws/strategy/aws_provider_strategy.py
+++ b/src/orb/providers/aws/strategy/aws_provider_strategy.py
@@ -7,16 +7,17 @@
architecture and single responsibility principle.
"""
+import asyncio
import time
from typing import TYPE_CHECKING, Any, Callable, Optional
from orb.domain.base.dependency_injection import injectable
+from orb.domain.base.operation_outcome import Accepted, Completed, Failed, OperationOutcome
from orb.domain.base.ports import LoggingPort
from orb.domain.base.ports.configuration_port import ConfigurationPort
# Import AWS-specific components
from orb.providers.aws.configuration.config import AWSProviderConfig
-from orb.providers.aws.exceptions.aws_exceptions import AWSConfigurationError
from orb.providers.aws.infrastructure.aws_client import AWSClient
from orb.providers.aws.services.capability_service import AWSCapabilityService
from orb.providers.aws.services.handler_registry import AWSHandlerRegistry
@@ -30,6 +31,7 @@
from orb.providers.aws.services.template_validation_service import AWSTemplateValidationService
if TYPE_CHECKING:
+ from orb.domain.request.aggregate import Request
from orb.monitoring.health import HealthCheck
from orb.providers.aws.infrastructure.adapters.aws_provisioning_adapter import (
AWSProvisioningAdapter,
@@ -409,8 +411,15 @@ async def _handle_describe_resource_instances(
logic for discovering instances from resource IDs. This delegates to the
correct handler's check_hosts_status method rather than using a generic
service that lacks per-handler context.
+
+ The handler returns a CheckHostsStatusResult containing both instance
+ details and a ProviderFulfilment verdict. The fulfilment is forwarded
+ in metadata so RequestStatusService can consume it without any
+ provider-specific logic.
"""
try:
+ from orb.domain.base.provider_fulfilment import ProviderFulfilment
+
resource_ids = operation.parameters.get("resource_ids", [])
provider_api = operation.parameters.get("provider_api", "RunInstances")
provider_api_value = (
@@ -442,25 +451,37 @@ async def _handle_describe_resource_instances(
operation.context.get("request_id") if operation.context else None
)
+ requested_count = int(operation.parameters.get("requested_count") or 1)
request = Request.create_new_request(
request_type=RequestType.ACQUIRE,
template_id=operation.parameters.get("template_id", "unknown"),
- machine_count=1,
+ machine_count=requested_count,
provider_type="aws",
provider_name="aws-default",
request_id=request_id,
)
request.resource_ids = resource_ids
- instance_details = handler.check_hosts_status(request)
+ # check_hosts_status makes blocking boto3 I/O calls (describe_fleet_instances,
+ # describe_instances, etc.). Running it directly in an async handler blocks
+ # the uvicorn event loop, which prevents uvicorn from accepting any further
+ # connections until the call completes. For large requests (e.g. 100 instances)
+ # this starvation causes all concurrent polls to fail with ConnectionError.
+ # Offloading to a thread pool executor keeps the event loop responsive.
+ check_result = await asyncio.to_thread(handler.check_hosts_status, request)
+ instance_details = check_result.instances
+ fulfilment: ProviderFulfilment = check_result.fulfilment
metadata: dict[str, Any] = {
"operation": "describe_resource_instances",
"resource_ids": resource_ids,
"provider_api": provider_api_value,
"instance_count": len(instance_details),
+ # Forward the provider's fulfilment verdict to the application layer.
+ # RequestStatusService reads this as "provider_fulfilment" and trusts
+ # it exclusively — no count math or AWS-specific key inspection.
+ "provider_fulfilment": fulfilment,
}
- self._augment_capacity_metadata(metadata, provider_api_value, resource_ids)
if not instance_details:
self._logger.info("No instances found for resources: %s", resource_ids)
@@ -477,7 +498,10 @@ async def _handle_describe_resource_instances(
"DESCRIBE_RESOURCE_INSTANCES_ERROR",
)
+ # -------------------------------------------------------------------------
# Infrastructure discovery methods (delegated to service)
+ # -------------------------------------------------------------------------
+
def discover_infrastructure(self, provider_config: dict[str, Any]) -> dict[str, Any]:
"""Discover AWS infrastructure for provider."""
return self._get_infrastructure_service().discover_infrastructure(provider_config)
@@ -559,7 +583,13 @@ def register_health_checks(self, health_check: HealthCheck) -> None:
return
from orb.providers.aws.health import register_aws_health_checks
- register_aws_health_checks(health_check, self.aws_client)
+ storage_strategy = "json"
+ if self._config_port is not None:
+ try:
+ storage_strategy = self._config_port.get_storage_strategy()
+ except Exception:
+ pass
+ register_aws_health_checks(health_check, self.aws_client, storage_strategy)
def cleanup(self) -> None:
"""Clean up AWS provider resources."""
@@ -571,100 +601,6 @@ def cleanup(self) -> None:
except Exception as e:
self._logger.warning("Failed during AWS provider cleanup: %s", e, exc_info=True)
- def _augment_capacity_metadata(
- self, metadata: dict[str, Any], provider_api: str, resource_ids: list[str]
- ) -> None:
- """Add fleet capacity fulfillment data to metadata for fleet-based providers."""
- if not resource_ids or not self.aws_client:
- return
- try:
- resource_id = resource_ids[0]
- capacity_fetchers: dict[str, Callable[[str], Optional[dict[str, Any]]]] = {
- "EC2Fleet": self._fetch_ec2_fleet_capacity,
- "SpotFleet": self._fetch_spot_fleet_capacity,
- "ASG": self._fetch_asg_capacity,
- }
- fetcher = capacity_fetchers.get(provider_api)
- if fetcher:
- result = fetcher(resource_id)
- if result is not None:
- metadata["fleet_capacity_fulfilment"] = result
- except Exception as e:
- self._logger.warning("Failed to augment capacity metadata: %s", e)
-
- def _fetch_ec2_fleet_capacity(self, resource_id: str) -> Optional[dict[str, Any]]:
- """Fetch capacity fulfillment data for an EC2Fleet resource."""
- if self.aws_client is None:
- raise AWSConfigurationError(
- "aws_client must be injected before calling _fetch_ec2_fleet_capacity"
- )
- response = self.aws_client.ec2_client.describe_fleets(FleetIds=[resource_id])
- fleets = response.get("Fleets", [])
- if not fleets:
- return None
- fleet = fleets[0]
- spec = fleet.get("TargetCapacitySpecification") or {}
- target = spec.get("TotalTargetCapacity")
- fulfilled = fleet.get("FulfilledCapacity") or 0
- fleet_type_val = (fleet.get("Type") or "maintain").lower()
- return {
- "target_capacity_units": target,
- "fulfilled_capacity_units": fulfilled,
- "provisioned_instance_count": int(fulfilled),
- "state": fleet.get("FleetState"),
- "fleet_type": fleet_type_val,
- "fulfillment_final": fleet_type_val == "instant",
- }
-
- def _fetch_spot_fleet_capacity(self, resource_id: str) -> Optional[dict[str, Any]]:
- """Fetch capacity fulfillment data for a SpotFleet resource."""
- if self.aws_client is None:
- raise AWSConfigurationError(
- "aws_client must be injected before calling _fetch_spot_fleet_capacity"
- )
- response = self.aws_client.ec2_client.describe_spot_fleet_requests(
- SpotFleetRequestIds=[resource_id]
- )
- configs = response.get("SpotFleetRequestConfigs", [])
- if not configs:
- return None
- cfg = configs[0].get("SpotFleetRequestConfig") or {}
- fulfilled = cfg.get("FulfilledCapacity") or 0
- return {
- "target_capacity_units": cfg.get("TargetCapacity"),
- "fulfilled_capacity_units": fulfilled,
- "provisioned_instance_count": int(fulfilled),
- "state": configs[0].get("SpotFleetRequestState"),
- "fleet_type": (cfg.get("Type") or "request").lower(),
- }
-
- def _fetch_asg_capacity(self, resource_id: str) -> Optional[dict[str, Any]]:
- """Fetch capacity fulfillment data for an Auto Scaling Group resource."""
- if self.aws_client is None:
- raise AWSConfigurationError(
- "aws_client must be injected before calling _fetch_asg_capacity"
- )
- response = self.aws_client.autoscaling_client.describe_auto_scaling_groups(
- AutoScalingGroupNames=[resource_id]
- )
- groups = response.get("AutoScalingGroups", [])
- if not groups:
- return None
- group = groups[0]
- instances = group.get("Instances") or []
- fulfilled = sum(
- int(inst.get("WeightedCapacity", 1))
- for inst in instances
- if inst.get("LifecycleState") == "InService"
- )
- provisioned = sum(1 for inst in instances if inst.get("LifecycleState") == "InService")
- return {
- "target_capacity_units": int(group.get("DesiredCapacity") or 0),
- "fulfilled_capacity_units": fulfilled,
- "provisioned_instance_count": provisioned,
- "state": group.get("Status"),
- }
-
async def _handle_resolve_image(self, operation: ProviderOperation) -> ProviderResult:
"""Handle image resolution using registry-based service."""
try:
@@ -712,6 +648,182 @@ def _create_image_resolution_service(self):
logger=self._logger,
)
+ # ------------------------------------------------------------------
+ # Typed provisioning interface — OperationOutcome
+ # ------------------------------------------------------------------
+
+ async def acquire(self, request: "Request") -> OperationOutcome:
+ """Submit an acquisition request to AWS.
+
+ AWS provider operations are asynchronous: the API call (EC2Fleet,
+ SpotFleet, RunInstances, ASG) returns a request/fleet ID immediately
+ while instances transition through ``pending``. The outcome is
+ therefore always ``Accepted`` on success.
+
+ Args:
+ request: Domain request describing resources to acquire.
+
+ Returns:
+ ``Accepted`` with pending instance IDs on success.
+ ``Failed`` on provider rejection or configuration error.
+ """
+ try:
+ # Build operation using the strategy-layer types already imported at the
+ # module level (ProviderOperation / ProviderOperationType from
+ # orb.providers.base.strategy). These are what execute_operation() expects.
+ operation = ProviderOperation(
+ operation_type=ProviderOperationType.CREATE_INSTANCES,
+ parameters={
+ "template_config": {},
+ "count": request.requested_count,
+ "request_id": str(request.request_id),
+ "request_metadata": dict(request.metadata),
+ },
+ context={
+ "correlation_id": str(request.request_id),
+ "request_id": str(request.request_id),
+ "dry_run": request.metadata.get("dry_run", False),
+ },
+ )
+ result = await self.execute_operation(operation)
+
+ if not result.success:
+ return Failed(
+ error=result.error_message or "AWS acquire failed",
+ recoverable=False,
+ )
+
+ resource_ids: list[str] = (result.data or {}).get("resource_ids", [])
+ request_id = str(request.request_id)
+
+ self._logger.info(
+ "AWS acquire accepted: request_id=%s, pending_resource_ids=%s",
+ request_id,
+ resource_ids,
+ )
+ return Accepted(
+ request_id=request_id,
+ pending_resource_ids=resource_ids,
+ metadata=result.metadata or {},
+ )
+
+ except Exception as exc:
+ self._logger.error("AWS acquire failed: %s", exc, exc_info=True)
+ return Failed(error=str(exc), recoverable=False)
+
+ async def return_machines(self, machine_ids: list[str], request: "Request") -> OperationOutcome:
+ """Submit a return (termination) request to AWS.
+
+ AWS terminates asynchronously — ``TerminateInstances`` returns
+ immediately while instances move through ``shutting-down``. The
+ outcome is therefore ``Accepted`` with the terminating IDs.
+
+ Args:
+ machine_ids: EC2 instance IDs to terminate.
+ request: Domain request providing context.
+
+ Returns:
+ ``Accepted`` with terminating instance IDs on success.
+ ``Failed`` on provider rejection or configuration error.
+ """
+ try:
+ operation = ProviderOperation(
+ operation_type=ProviderOperationType.TERMINATE_INSTANCES,
+ parameters={
+ "instance_ids": machine_ids,
+ "request_id": str(request.request_id),
+ "template_id": request.template_id,
+ "provider_api": request.provider_api or "RunInstances",
+ },
+ context={
+ "correlation_id": str(request.request_id),
+ "request_id": str(request.request_id),
+ },
+ )
+ result = await self.execute_operation(operation)
+
+ if not result.success:
+ return Failed(
+ error=result.error_message or "AWS return_machines failed",
+ recoverable=False,
+ )
+
+ self._logger.info(
+ "AWS termination accepted: request_id=%s, terminating=%s",
+ request.request_id,
+ machine_ids,
+ )
+ return Accepted(
+ request_id=str(request.request_id),
+ pending_resource_ids=list(machine_ids),
+ metadata=result.metadata or {},
+ )
+
+ except Exception as exc:
+ self._logger.error("AWS return_machines failed: %s", exc, exc_info=True)
+ return Failed(error=str(exc), recoverable=False)
+
+ async def get_status(self, resource_ids: list[str], request: "Request") -> OperationOutcome:
+ """Query AWS instance status for previously submitted resources.
+
+ Returns ``Completed`` only when *all* instances have reached a
+ terminal state (``running`` for acquire, ``terminated`` for return).
+ Returns ``Accepted`` (still in-progress) otherwise.
+
+ Args:
+ resource_ids: EC2 instance or resource IDs to check.
+ request: Domain request providing context.
+
+ Returns:
+ ``Completed`` when all instances are terminal.
+ ``Accepted`` when one or more instances are still transitioning.
+ ``Failed`` when all instances failed or a hard error occurred.
+ """
+ try:
+ operation = ProviderOperation(
+ operation_type=ProviderOperationType.GET_INSTANCE_STATUS,
+ parameters={
+ "instance_ids": resource_ids,
+ "template_id": request.template_id,
+ "provider_api": request.provider_api or "RunInstances",
+ },
+ context={
+ "correlation_id": str(request.request_id),
+ "request_id": str(request.request_id),
+ },
+ )
+ result = await self.execute_operation(operation)
+
+ if not result.success:
+ return Failed(
+ error=result.error_message or "AWS get_status failed",
+ recoverable=True,
+ )
+
+ instances: list[dict[str, Any]] = (result.data or {}).get("instances", [])
+ terminal_states = frozenset({"running", "terminated", "stopped", "failed"})
+ non_terminal = [
+ inst for inst in instances if inst.get("status", "") not in terminal_states
+ ]
+
+ if non_terminal:
+ still_pending = [inst.get("instance_id", "") for inst in non_terminal]
+ return Accepted(
+ request_id=str(request.request_id),
+ pending_resource_ids=still_pending,
+ metadata=result.metadata or {},
+ )
+
+ completed_ids = [inst.get("instance_id", "") for inst in instances]
+ return Completed(
+ resource_ids=completed_ids,
+ metadata=result.metadata or {},
+ )
+
+ except Exception as exc:
+ self._logger.error("AWS get_status failed: %s", exc, exc_info=True)
+ return Failed(error=str(exc), recoverable=True)
+
def __str__(self) -> str:
"""Return string representation for debugging."""
return f"AWSProviderStrategy(region={self._aws_config.region}, initialized={self._initialized})"
diff --git a/src/orb/providers/aws/value_objects.py b/src/orb/providers/aws/value_objects.py
new file mode 100644
index 000000000..d9c0cea71
--- /dev/null
+++ b/src/orb/providers/aws/value_objects.py
@@ -0,0 +1,19 @@
+"""Public AWS provider value objects.
+
+Re-exports AWS-specific value objects from their canonical locations so that
+callers outside the internal sub-packages can use a stable, shallow import path:
+
+ from orb.providers.aws.value_objects import AWSAllocationStrategy
+"""
+
+from orb.providers.aws.domain.template.value_objects import (
+ CANONICAL_ALLOCATION_STRATEGIES,
+ AWSAllocationStrategy,
+ normalise_allocation_strategy,
+)
+
+__all__: list[str] = [
+ "AWSAllocationStrategy",
+ "CANONICAL_ALLOCATION_STRATEGIES",
+ "normalise_allocation_strategy",
+]
diff --git a/src/orb/providers/base/strategy/base_provider_strategy.py b/src/orb/providers/base/strategy/base_provider_strategy.py
index 09078bfc2..bba01542c 100644
--- a/src/orb/providers/base/strategy/base_provider_strategy.py
+++ b/src/orb/providers/base/strategy/base_provider_strategy.py
@@ -1,10 +1,14 @@
"""Base provider strategy implementing ProviderPort."""
-from abc import ABC
-from typing import Any
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING, Any
+from orb.domain.base.operation_outcome import OperationOutcome
from orb.domain.base.ports.provider_port import ProviderPort
+if TYPE_CHECKING:
+ from orb.domain.request.aggregate import Request
+
class BaseProviderStrategy(ProviderPort, ABC):
"""Base class for all provider strategies implementing ProviderPort."""
@@ -26,3 +30,58 @@ def get_provider_info(self) -> dict[str, Any]:
@classmethod
def get_defaults_config(cls) -> dict:
return {}
+
+ # ------------------------------------------------------------------
+ # Typed provisioning interface — returns OperationOutcome
+ # ------------------------------------------------------------------
+
+ @abstractmethod
+ async def acquire(self, request: "Request") -> OperationOutcome:
+ """Submit an acquisition request to the provider.
+
+ The provider *accepts* the request and returns immediately. Because
+ most cloud providers (AWS EC2Fleet, SpotFleet, …) are async by nature
+ the outcome is typically ``Accepted`` rather than ``Completed``.
+ Callers must poll via :meth:`get_status` until a terminal outcome.
+
+ Args:
+ request: Domain request describing what resources to acquire.
+
+ Returns:
+ ``Accepted`` — provider accepted, instances pending.
+ ``Completed`` — provider fulfilled synchronously (rare, e.g. dry-run).
+ ``Failed`` — provider rejected or hard error.
+ """
+
+ @abstractmethod
+ async def return_machines(self, machine_ids: list[str], request: "Request") -> OperationOutcome:
+ """Submit a return (termination) request to the provider.
+
+ AWS terminates asynchronously — the API call returns immediately while
+ instances transition through ``shutting-down``. The outcome is
+ therefore ``Accepted`` with the terminating IDs as ``pending_resource_ids``.
+
+ Args:
+ machine_ids: Provider-side instance/resource IDs to terminate.
+ request: Domain request providing context (provider_name, template, …).
+
+ Returns:
+ ``Accepted`` — termination accepted, instances shutting down.
+ ``Completed`` — terminated synchronously (rare / mock).
+ ``Failed`` — provider rejected or hard error.
+ """
+
+ @abstractmethod
+ async def get_status(self, resource_ids: list[str], request: "Request") -> OperationOutcome:
+ """Query the current status of previously submitted resources.
+
+ Args:
+ resource_ids: Provider-side resource IDs to check.
+ request: Domain request providing context.
+
+ Returns:
+ ``Completed`` — all resources reached a terminal success state.
+ ``Accepted`` — resources still in a non-terminal state.
+ ``RequiresFollowUp`` — a specific follow-up action is needed.
+ ``Failed`` — all resources failed or a hard error occurred.
+ """
diff --git a/src/orb/providers/registration.py b/src/orb/providers/registration.py
index cfd58e634..b92d4fb89 100644
--- a/src/orb/providers/registration.py
+++ b/src/orb/providers/registration.py
@@ -1,20 +1,127 @@
-"""Provider registration functions."""
+"""Provider registration functions.
+To add a new provider:
+ 1. Add its name to ``_REGISTERED_PROVIDERS`` below (one line).
+ 2. Create ``src/orb/providers//registration.py`` that exposes:
+ - ``register__provider(registry)`` – registers strategy + factories
+ - ``initialize__provider(container)`` – wires DI services (optional)
-def register_all_provider_types() -> None:
- """Register all available provider types."""
+That is the only edit outside the new provider package.
+"""
+
+from __future__ import annotations
+
+import importlib
+import importlib.util
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from orb.infrastructure.di.container import DIContainer
+
+# ---------------------------------------------------------------------------
+# Central provider list – the single line to edit when adding a new provider.
+# ---------------------------------------------------------------------------
+_REGISTERED_PROVIDERS: list[str] = ["aws"]
+
+
+def register_all_providers(container: "DIContainer | None" = None) -> None:
+ """Register all providers listed in ``_REGISTERED_PROVIDERS``.
+
+ For each provider name ``n`` this function:
+ 1. Imports ``orb.providers..registration`` (skips silently on ImportError
+ so that optional provider extras are handled gracefully).
+ 2. Calls ``register__provider(registry)`` to register the strategy and
+ supporting factories with the global provider registry.
+ 3. If *container* is given, calls ``initialize__provider(container)``
+ when that function exists in the module.
+
+ Args:
+ container: Optional DI container. When supplied, per-provider DI
+ wiring is performed in the same call; when omitted only the
+ registry-level registration is performed.
+ """
from orb.providers.registry import get_provider_registry
registry = get_provider_registry()
- # Register AWS provider
- from orb.providers.aws.registration import register_aws_provider
+ for name in _REGISTERED_PROVIDERS:
+ module_path = f"orb.providers.{name}.registration"
+ if importlib.util.find_spec(module_path) is None:
+ continue
+ try:
+ mod = importlib.import_module(module_path)
+ except ImportError:
+ continue
+
+ # Registry-level registration
+ register_fn = getattr(mod, f"register_{name}_provider", None)
+ if register_fn is not None:
+ register_fn(registry)
+
+ # DI-level initialisation (only when a container is supplied)
+ if container is not None:
+ init_fn = getattr(mod, f"initialize_{name}_provider", None)
+ if init_fn is not None:
+ init_fn(container)
+
+
+# ---------------------------------------------------------------------------
+# Deprecated aliases – kept for backward compatibility with existing callers.
+# ---------------------------------------------------------------------------
+
+
+def register_all_provider_types() -> None:
+ """Register all available provider types.
+
+ Deprecated: use ``register_all_providers()`` instead. Kept as a
+ backward-compatible alias so existing callers continue to work without
+ modification.
+ """
+ register_all_providers(container=None)
+
+
+def register_all_provider_cli_specs() -> None:
+ """Register CLI argument specs for all available providers.
+
+ Lightweight bootstrap that only registers CLI specs so that
+ ``build_parser`` can call it before any application context exists.
+
+ Deprecated: ``register_all_providers()`` now handles CLI spec registration
+ as part of ``initialize__provider``. This alias is retained so that
+ ``cli/args.py`` and other early-bootstrap callers continue to work.
+ """
+ from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
+
+ try:
+ from orb.providers.aws.cli.aws_cli_spec import AWSCLISpec
+
+ if CLISpecRegistry.get("aws") is None:
+ CLISpecRegistry.register("aws", AWSCLISpec())
+ except ImportError:
+ pass # [aws] extra not installed; AWS CLI spec unavailable
+
+
+def register_all_defaults_loaders() -> None:
+ """Register defaults loaders for all available providers.
+
+ Lightweight bootstrap that only registers ``ProviderDefaultsLoaderPort``
+ implementations so that ``ConfigurationLoader._load_strategy_defaults`` can
+ call it before a full application context has been set up.
+
+ Deprecated: ``register_all_providers()`` now handles defaults-loader
+ registration as part of ``initialize__provider``. This alias is
+ retained so that ``config/loader.py`` and other early-bootstrap callers
+ continue to work.
+ """
+ from orb.providers.registry.defaults_loader_registry import DefaultsLoaderRegistry
- register_aws_provider(registry)
+ if DefaultsLoaderRegistry.get("aws") is None:
+ try:
+ from orb.providers.aws.defaults_loader import AWSDefaultsLoader
- # Future providers would be added here
- # register_provider1_provider(registry)
- # register_provider2_provider(registry)
+ DefaultsLoaderRegistry.register("aws", AWSDefaultsLoader())
+ except ImportError:
+ pass # [aws] extra not installed; AWS defaults loader unavailable
def register_fallback_provider(
diff --git a/src/orb/providers/registry/__init__.py b/src/orb/providers/registry/__init__.py
index 72417c075..bd3f0a606 100644
--- a/src/orb/providers/registry/__init__.py
+++ b/src/orb/providers/registry/__init__.py
@@ -1,5 +1,6 @@
"""Provider registry package - re-exports all public symbols for backward compatibility."""
+from orb.providers.registry.defaults_loader_registry import DefaultsLoaderRegistry
from orb.providers.registry.provider_registry import ProviderRegistry, get_provider_registry
from orb.providers.registry.types import (
ProviderFactoryInterface,
@@ -8,6 +9,7 @@
)
__all__ = [
+ "DefaultsLoaderRegistry",
"ProviderRegistry",
"get_provider_registry",
"ProviderFactoryInterface",
diff --git a/src/orb/providers/registry/defaults_loader_registry.py b/src/orb/providers/registry/defaults_loader_registry.py
new file mode 100644
index 000000000..bea0e8fcd
--- /dev/null
+++ b/src/orb/providers/registry/defaults_loader_registry.py
@@ -0,0 +1,54 @@
+"""Registry mapping provider types to ProviderDefaultsLoaderPort instances."""
+
+from __future__ import annotations
+
+from orb.domain.base.ports.provider_defaults_loader_port import ProviderDefaultsLoaderPort
+
+
+class DefaultsLoaderRegistry:
+ """Simple class-variable registry mapping provider type strings to
+ ``ProviderDefaultsLoaderPort`` implementations.
+
+ Follows the same lightweight pattern as ``CLISpecRegistry`` and
+ ``FieldMappingRegistry``.
+
+ Usage::
+
+ # During provider bootstrap:
+ DefaultsLoaderRegistry.register("aws", AWSDefaultsLoader())
+
+ # At call site:
+ for provider_type, loader in DefaultsLoaderRegistry.all().items():
+ defaults = loader.load_defaults()
+ """
+
+ _loaders: dict[str, ProviderDefaultsLoaderPort] = {}
+
+ @classmethod
+ def register(cls, provider_type: str, loader: ProviderDefaultsLoaderPort) -> None:
+ """Register a defaults loader for *provider_type*.
+
+ Registration is idempotent — re-registering the same provider type
+ silently overwrites the previous entry.
+ """
+ cls._loaders[provider_type] = loader
+
+ @classmethod
+ def get(cls, provider_type: str) -> ProviderDefaultsLoaderPort | None:
+ """Return the loader for *provider_type*, or ``None`` if not registered."""
+ return cls._loaders.get(provider_type)
+
+ @classmethod
+ def all(cls) -> dict[str, ProviderDefaultsLoaderPort]:
+ """Return all registered loaders keyed by provider type."""
+ return dict(cls._loaders)
+
+ @classmethod
+ def registered_providers(cls) -> list[str]:
+ """Return all registered provider type strings."""
+ return list(cls._loaders.keys())
+
+ @classmethod
+ def clear(cls) -> None:
+ """Remove all registrations (primarily for use in tests)."""
+ cls._loaders.clear()
diff --git a/src/orb/providers/registry/provider_registry.py b/src/orb/providers/registry/provider_registry.py
index aeda55e18..24b51d266 100644
--- a/src/orb/providers/registry/provider_registry.py
+++ b/src/orb/providers/registry/provider_registry.py
@@ -1,9 +1,16 @@
"""Provider Registry - Registry pattern for provider strategy factories."""
import importlib
+import re
import threading
from typing import Any, Callable, List, Optional
+# Only allow simple snake_case identifiers as provider types to prevent
+# module-injection via crafted provider_type strings (e.g. containing dots
+# or path-traversal sequences) that would be interpolated directly into the
+# dynamic importlib.import_module() call.
+_VALID_PROVIDER_TYPE_RE = re.compile(r"^[a-z][a-z0-9_]*$")
+
from orb.domain.base.exceptions import ConfigurationError
from orb.domain.base.ports.configuration_port import ConfigurationPort
from orb.domain.base.ports.provider_registry_port import ProviderRegistryPort
@@ -143,6 +150,9 @@ def ensure_provider_type_registered(self, provider_type: str) -> bool:
return True
# Try to dynamically import and register
+ if not _VALID_PROVIDER_TYPE_RE.match(provider_type):
+ raise ValueError(f"Invalid provider type: {provider_type!r}")
+
module_name = f"orb.providers.{provider_type}.registration"
try:
if self._logger:
@@ -208,6 +218,9 @@ def ensure_provider_instance_registered_from_config(self, provider_instance: Any
try:
provider_type = provider_instance.type
+ if not _VALID_PROVIDER_TYPE_RE.match(provider_type):
+ raise ValueError(f"Invalid provider type: {provider_type!r}")
+
if self._logger:
self._logger.debug("Registering provider instance: %s", provider_instance.name)
@@ -238,6 +251,7 @@ def register( # type: ignore[override]
resolver_factory: Optional[Callable] = None,
validator_factory: Optional[Callable] = None,
strategy_class: Optional[type] = None,
+ default_api: Optional[str] = None,
**kwargs: Any,
) -> None:
"""Register provider type - implements abstract method."""
@@ -249,10 +263,28 @@ def register( # type: ignore[override]
resolver_factory=resolver_factory,
validator_factory=validator_factory,
strategy_class=strategy_class,
+ default_api=default_api,
)
except ValueError as e:
raise ConfigurationError(str(e))
+ def get_default_api(self, provider_type: str) -> Optional[str]:
+ """Return the default API name for the given provider type, or None if not set.
+
+ Args:
+ provider_type: Type identifier for the provider (e.g., 'aws')
+
+ Returns:
+ Default API string from registration, or None if not registered / not set.
+ """
+ try:
+ registration = self._get_type_registration(provider_type)
+ if isinstance(registration, ProviderRegistration):
+ return registration.default_api
+ except (ValueError, KeyError):
+ pass
+ return None
+
def register_provider(
self,
provider_type: str,
@@ -261,6 +293,7 @@ def register_provider(
resolver_factory: Optional[Callable] = None,
validator_factory: Optional[Callable] = None,
strategy_class: Optional[type] = None,
+ default_api: Optional[str] = None,
) -> None:
"""
Register a provider with its factory functions - backward compatibility method.
@@ -271,6 +304,8 @@ def register_provider(
config_factory: Factory function to create provider configuration
resolver_factory: Optional factory for template resolver
validator_factory: Optional factory for template validator
+ strategy_class: Optional provider strategy class
+ default_api: Optional default API name contributed by this provider
Raises:
ValueError: If provider_type is already registered
@@ -282,6 +317,7 @@ def register_provider(
resolver_factory,
validator_factory,
strategy_class=strategy_class,
+ default_api=default_api,
)
def register_provider_instance(
@@ -853,6 +889,7 @@ def _create_registration(
additional_factories.get("resolver_factory"),
additional_factories.get("validator_factory"),
strategy_class=additional_factories.get("strategy_class"),
+ default_api=additional_factories.get("default_api"),
)
@staticmethod
diff --git a/src/orb/providers/registry/types.py b/src/orb/providers/registry/types.py
index fdb74a06a..9b19373ea 100644
--- a/src/orb/providers/registry/types.py
+++ b/src/orb/providers/registry/types.py
@@ -33,6 +33,7 @@ def __init__(
resolver_factory: Optional[Callable] = None,
validator_factory: Optional[Callable] = None,
strategy_class: Optional[type] = None,
+ default_api: Optional[str] = None,
) -> None:
"""Initialize the instance."""
super().__init__(
@@ -45,3 +46,4 @@ def __init__(
self.resolver_factory = resolver_factory
self.validator_factory = validator_factory
self.strategy_class = strategy_class
+ self.default_api = default_api
diff --git a/src/orb/sdk/client.py b/src/orb/sdk/client.py
index 60a27a1c5..3ec5684e9 100644
--- a/src/orb/sdk/client.py
+++ b/src/orb/sdk/client.py
@@ -477,12 +477,28 @@ async def request_machines(self, template_id: str, count: int, **kwargs) -> dict
return scheduler.format_request_response(raw)
return raw
- async def get_request_status(self, request_ids: list, **kwargs) -> dict:
- """Get status for one or more requests via GetRequestStatusOrchestrator."""
+ async def get_request_status(
+ self,
+ request_ids: "list | None" = None,
+ *,
+ request_id: "str | None" = None,
+ **kwargs,
+ ) -> dict:
+ """Get status for one or more requests via GetRequestStatusOrchestrator.
+
+ Accepts either ``request_ids`` (list) or ``request_id`` (str, singular alias
+ for single-ID callers). Both are merged into a single list before dispatch.
+ """
if not self._initialized:
raise SDKError("SDK not initialized. Use as async context manager.")
assert self._container is not None
+ # Resolve the list of IDs from either parameter name.
+ ids: list = list(request_ids) if request_ids is not None else []
+ if request_id is not None:
+ if request_id not in ids:
+ ids.append(request_id)
+
from orb.application.ports.scheduler_port import SchedulerPort
from orb.application.services.orchestration.dtos import GetRequestStatusInput
from orb.application.services.orchestration.get_request_status import (
@@ -492,7 +508,7 @@ async def get_request_status(self, request_ids: list, **kwargs) -> dict:
orchestrator = self._container.get(GetRequestStatusOrchestrator)
result = await orchestrator.execute(
GetRequestStatusInput(
- request_ids=list(request_ids),
+ request_ids=ids,
all_requests=kwargs.get("all_requests", False),
verbose=kwargs.get("verbose", False),
)
diff --git a/src/orb/sdk/config.py b/src/orb/sdk/config.py
index bc718a708..eca1eb98f 100644
--- a/src/orb/sdk/config.py
+++ b/src/orb/sdk/config.py
@@ -70,7 +70,19 @@ def from_dict(cls, config: dict[str, Any]) -> "SDKConfig":
for key, value in config.items():
if key in known_fields:
- sdk_config[key] = value
+ # ``scheduler`` is a plain string override in SDKConfig (e.g. "default"
+ # or "hostfactory"). When loading from an ORB config.json the top-level
+ # ``scheduler`` key is the full scheduler sub-config dict
+ # ({"type": "hostfactory", "config_root": "..."}). Ingesting that dict
+ # as the string override causes ConfigurationManager.override_scheduler_strategy
+ # to store a dict, which then propagates as the scheduler_type into the
+ # registry lookup and fails with "unhashable type: 'dict'".
+ if key == "scheduler" and isinstance(value, dict):
+ # The ORB config scheduler object is not an SDK string override;
+ # skip it so the scheduler type is resolved from the config file.
+ custom_config[key] = value
+ else:
+ sdk_config[key] = value
else:
custom_config[key] = value
diff --git a/src/orb/sdk/protocols.py b/src/orb/sdk/protocols.py
index d9b380be4..b4c4dfee1 100644
--- a/src/orb/sdk/protocols.py
+++ b/src/orb/sdk/protocols.py
@@ -48,7 +48,13 @@ async def refresh_templates(self, **kwargs: Any) -> dict[str, Any]: # type: ign
pass
# --- Request operations ---
- async def get_request_status(self, request_ids: list[str], **kwargs: Any) -> dict[str, Any]: # type: ignore[return]
+ async def get_request_status(
+ self,
+ request_ids: "list[str] | None" = None,
+ *,
+ request_id: "str | None" = None,
+ **kwargs: Any,
+ ) -> dict[str, Any]: # type: ignore[return]
pass
async def get_request(self, *, request_id: str, **kwargs: Any) -> dict[str, Any]: # type: ignore[return]
diff --git a/tests/conftest.py b/tests/conftest.py
index b80e9cab4..f6d35b62f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,7 +8,25 @@ def pytest_addoption(parser):
"--run-aws",
action="store_true",
default=False,
- help="Run tests requiring real AWS credentials",
+ help="Alias for --live (backward compat): run tests requiring real AWS credentials",
+ )
+ parser.addoption(
+ "--live",
+ action="store_true",
+ default=False,
+ help="Enable tests under tests/providers//live/ (require real credentials)",
+ )
+ parser.addoption(
+ "--no-mocked",
+ action="store_true",
+ default=False,
+ help="Skip mocked/moto tests under tests/providers//moto/",
+ )
+ parser.addoption(
+ "--provider",
+ action="store",
+ default=None,
+ help="Filter to tests under tests/providers// only (e.g. --provider aws)",
)
parser.addoption(
"--keep-logs",
@@ -18,15 +36,74 @@ def pytest_addoption(parser):
)
+def _is_live_test(item) -> bool:
+ """Return True when the test lives under a providers//live/ subtree."""
+ path = str(item.fspath)
+ return "/providers/" in path and "/live/" in path
+
+
+def _is_moto_test(item) -> bool:
+ """Return True when the test lives under a providers//moto/ subtree."""
+ path = str(item.fspath)
+ return "/providers/" in path and "/moto/" in path
+
+
def pytest_collection_modifyitems(config, items):
- if config.getoption("--run-aws"):
- return
- if any("onaws" in str(a) for a in config.args):
- return
- skip = pytest.mark.skip(reason="requires real AWS credentials — pass --run-aws to run")
+ live_enabled = config.getoption("--live") or config.getoption("--run-aws")
+ no_mocked = config.getoption("--no-mocked")
+ provider_filter = config.getoption("--provider")
+
+ # Also honour legacy path-based live detection (e.g. pytest tests/providers/aws/live)
+ explicit_live = any("live" in str(a) or "onaws" in str(a) for a in config.args)
+ if explicit_live:
+ live_enabled = True
+
+ skip_live = pytest.mark.skip(
+ reason="requires real credentials — pass --live (or --run-aws) to run"
+ )
+ skip_mocked = pytest.mark.skip(reason="moto tests skipped — remove --no-mocked to run")
+ skip_provider = pytest.mark.skip(reason=f"filtered to --provider {provider_filter}")
+
+ # Auto-skip AWS provider tests when boto3 is not installed
+ try:
+ import boto3 as _boto3 # noqa: F401
+
+ aws_available = True
+ except ImportError:
+ aws_available = False
+
+ skip_no_aws = pytest.mark.skip(reason="[aws] extra not installed — boto3 unavailable")
+
for item in items:
- if item.get_closest_marker("aws") and not item.get_closest_marker("provider_contract"):
- item.add_marker(skip)
+ path = str(item.fspath)
+
+ # Auto-skip AWS provider tests when [aws] extra is not installed
+ if not aws_available and "/providers/aws/" in path:
+ item.add_marker(skip_no_aws)
+
+ # Provider filter
+ if provider_filter and f"/providers/{provider_filter}" not in path:
+ if "/providers/" in path:
+ item.add_marker(skip_provider)
+
+ # Live gate
+ if _is_live_test(item) and not live_enabled:
+ item.add_marker(skip_live)
+ continue
+
+ # Moto gate
+ if _is_moto_test(item) and no_mocked:
+ item.add_marker(skip_mocked)
+ continue
+
+ # Legacy marker-based gate (non-provider-tree tests marked @pytest.mark.aws)
+ if (
+ item.get_closest_marker("aws")
+ and not item.get_closest_marker("provider_contract")
+ and not live_enabled
+ and not _is_live_test(item)
+ ):
+ item.add_marker(skip_live)
import json
@@ -59,7 +136,12 @@ def mock_aws(): # type: ignore[misc]
yield
-import boto3
+try:
+ import boto3
+
+ AWS_AVAILABLE = True
+except ImportError:
+ AWS_AVAILABLE = False
from orb.config.manager import ConfigurationManager
from orb.config.schemas.app_schema import AppConfig
@@ -223,18 +305,24 @@ def aws_mocks():
@pytest.fixture
def ec2_client(aws_mocks):
"""Create a mocked EC2 client."""
+ if not AWS_AVAILABLE:
+ pytest.skip("boto3 not installed — install [aws] extra")
return boto3.client("ec2", region_name="us-east-1")
@pytest.fixture
def autoscaling_client(aws_mocks):
"""Create a mocked Auto Scaling client."""
+ if not AWS_AVAILABLE:
+ pytest.skip("boto3 not installed — install [aws] extra")
return boto3.client("autoscaling", region_name="us-east-1")
@pytest.fixture
def ssm_client(aws_mocks):
"""Create a mocked SSM client."""
+ if not AWS_AVAILABLE:
+ pytest.skip("boto3 not installed — install [aws] extra")
return boto3.client("ssm", region_name="us-east-1")
diff --git a/tests/contract/test_default_contract.py b/tests/contract/test_default_contract.py
index 46000454a..aff203032 100644
--- a/tests/contract/test_default_contract.py
+++ b/tests/contract/test_default_contract.py
@@ -1,7 +1,7 @@
"""Default scheduler boundary contract tests.
Validates that every response ORB emits through the default scheduler
-interface conforms to the schemas defined in tests/onaws/plugin_io_schemas.py.
+interface conforms to the schemas defined in tests/providers/aws/live/plugin_io_schemas.py.
"""
import re
@@ -17,7 +17,7 @@
except ImportError:
pytest.skip("jsonschema not installed", allow_module_level=True)
-from tests.onaws.plugin_io_schemas import (
+from tests.providers.aws.live.plugin_io_schemas import (
expected_get_available_templates_schema_default,
expected_request_machines_schema_default,
expected_request_status_schema_default,
diff --git a/tests/contract/test_hf_contract.py b/tests/contract/test_hf_contract.py
index a56383757..9624c8d2a 100644
--- a/tests/contract/test_hf_contract.py
+++ b/tests/contract/test_hf_contract.py
@@ -1,7 +1,7 @@
"""HostFactory plugin boundary contract tests (Boundary A).
Validates that every response ORB emits through the HF plugin interface
-conforms to the schemas defined in tests/onaws/plugin_io_schemas.py.
+conforms to the schemas defined in tests/providers/aws/live/plugin_io_schemas.py.
The strictest contract is request_machines: additionalProperties=False means
any extra field added to that response immediately breaks HF.
@@ -23,7 +23,7 @@
except ImportError:
pytest.skip("jsonschema not installed", allow_module_level=True)
-from tests.onaws.plugin_io_schemas import (
+from tests.providers.aws.live.plugin_io_schemas import (
expected_get_available_templates_schema_hostfactory,
expected_request_machines_schema_hostfactory,
expected_request_status_schema_hostfactory,
diff --git a/tests/integration/test_clean_architecture_integration.py b/tests/integration/test_clean_architecture_integration.py
index fc1fdafba..ad7b92490 100644
--- a/tests/integration/test_clean_architecture_integration.py
+++ b/tests/integration/test_clean_architecture_integration.py
@@ -6,9 +6,9 @@
from orb.application.services.template_defaults_service import TemplateDefaultsService
from orb.domain.base.ports.logging_port import LoggingPort
-from orb.domain.template.extensions import TemplateExtensionRegistry
from orb.domain.template.factory import TemplateFactory
from orb.domain.template.template_aggregate import Template
+from orb.infrastructure.registry.template_extension_registry import TemplateExtensionRegistry
from orb.providers.aws.configuration.template_extension import AWSTemplateExtensionConfig
@@ -85,22 +85,36 @@ def template_defaults_service(self, mock_config_manager, mock_logger, template_f
def test_aws_extension_registration(self):
"""Test that AWS extensions are properly registered."""
- # Clear registry for clean test
- TemplateExtensionRegistry.clear_registry()
-
- # Register AWS extension
- TemplateExtensionRegistry.register_extension("aws", AWSTemplateExtensionConfig)
-
- # Verify registration
- assert TemplateExtensionRegistry.has_extension("aws")
- assert TemplateExtensionRegistry.get_extension_class("aws") == AWSTemplateExtensionConfig
+ # Snapshot both class and instance registries before mutating them so we
+ # can restore them afterwards. clear_registry() + selective re-register
+ # must not leak state into subsequent tests.
+ saved_extensions = dict(TemplateExtensionRegistry._extensions)
+ saved_instances = dict(TemplateExtensionRegistry._extension_instances)
+ try:
+ # Clear registry for clean test
+ TemplateExtensionRegistry.clear_registry()
+
+ # Register AWS extension
+ TemplateExtensionRegistry.register_extension("aws", AWSTemplateExtensionConfig)
+
+ # Verify registration
+ assert TemplateExtensionRegistry.has_extension("aws")
+ assert (
+ TemplateExtensionRegistry.get_extension_class("aws") == AWSTemplateExtensionConfig
+ )
- # Test extension defaults - must provide non-empty config_data or it returns {}
- extension_defaults = TemplateExtensionRegistry.get_extension_defaults(
- "aws", {"allocation_strategy": "capacity_optimized"}
- )
- assert isinstance(extension_defaults, dict)
- assert "allocation_strategy" in extension_defaults
+ # Test extension defaults - must provide non-empty config_data or it returns {}
+ extension_defaults = TemplateExtensionRegistry.get_extension_defaults(
+ "aws", {"allocation_strategy": "capacity_optimized"}
+ )
+ assert isinstance(extension_defaults, dict)
+ assert "allocation_strategy" in extension_defaults
+ finally:
+ # Restore the registry to the pre-test state unconditionally.
+ TemplateExtensionRegistry._extensions.clear()
+ TemplateExtensionRegistry._extensions.update(saved_extensions)
+ TemplateExtensionRegistry._extension_instances.clear()
+ TemplateExtensionRegistry._extension_instances.update(saved_instances)
def test_clean_template_schema_validation(self):
"""Test that cleaned template schema works correctly."""
diff --git a/tests/integration/test_cqrs_migration_baseline.py b/tests/integration/test_cqrs_migration_baseline.py
index 179b5b826..cba58fff0 100644
--- a/tests/integration/test_cqrs_migration_baseline.py
+++ b/tests/integration/test_cqrs_migration_baseline.py
@@ -203,6 +203,17 @@ def create_request_handler(
is_final=True,
)
)
+ from orb.domain.base.results import ProviderSelectionResult
+
+ mock_provider_validation_service = AsyncMock()
+ mock_provider_validation_service.select_and_validate_provider = AsyncMock(
+ return_value=ProviderSelectionResult(
+ provider_type="aws",
+ provider_name="aws-default",
+ selection_reason="Best match for template requirements",
+ confidence=0.95,
+ )
+ )
return CreateMachineRequestHandler(
uow_factory=mock_uow_factory,
logger=mock_logger,
@@ -212,6 +223,7 @@ def create_request_handler(
query_bus=mock_query_bus,
provider_selection_port=mock_provider_selection_port,
provisioning_service=mock_provisioning_service,
+ provider_validation_service=mock_provider_validation_service,
)
@pytest.fixture
@@ -240,7 +252,7 @@ async def test_create_request_command_handler(self, create_request_handler):
# Verify handler interactions
create_request_handler._query_bus.execute.assert_called_once()
- create_request_handler._provider_selection_port.select_provider_for_template.assert_called_once()
+ create_request_handler._provider_validation_service.select_and_validate_provider.assert_called_once()
create_request_handler._provisioning_service.execute_provisioning.assert_called_once()
@pytest.mark.asyncio
diff --git a/tests/integration/test_security_features.py b/tests/integration/test_security_features.py
index 6b71470ce..cb29e4172 100644
--- a/tests/integration/test_security_features.py
+++ b/tests/integration/test_security_features.py
@@ -1,4 +1,4 @@
-"""Integration tests for security features: JWT blacklist, input validation, auth middleware."""
+"""Integration tests for security features: JWT denylist, input validation, auth middleware."""
import time
from unittest.mock import AsyncMock, MagicMock
@@ -11,7 +11,7 @@
EnhancedBearerTokenStrategy,
RateLimiter,
)
-from orb.infrastructure.auth.token_blacklist import InMemoryTokenBlacklist, RedisTokenBlacklist
+from orb.infrastructure.auth.token_denylist import InMemoryTokenDenylist, RedisTokenDenylist
from orb.infrastructure.validation.input_validator import InputValidator, ValidationError
# ---------------------------------------------------------------------------
@@ -36,15 +36,15 @@ def _make_context(
def _make_strategy(
- blacklist=None,
+ denylist=None,
rate_limit_enabled: bool = False,
token_expiry: int = 3600,
) -> EnhancedBearerTokenStrategy:
- if blacklist is None:
- blacklist = InMemoryTokenBlacklist()
+ if denylist is None:
+ denylist = InMemoryTokenDenylist()
return EnhancedBearerTokenStrategy(
secret_key=SECRET,
- blacklist=blacklist,
+ denylist=denylist,
algorithm="HS256",
token_expiry=token_expiry,
enabled=True,
@@ -53,18 +53,18 @@ def _make_strategy(
# ===========================================================================
-# JWT Token Blacklist – InMemory
+# JWT Token Denylist – InMemory
# ===========================================================================
-class TestInMemoryBlacklistIntegration:
- """Integration tests for InMemoryTokenBlacklist."""
+class TestInMemoryDenylistIntegration:
+ """Integration tests for InMemoryTokenDenylist."""
@pytest.mark.asyncio
async def test_revoke_then_reject(self):
- """Token added to blacklist must be rejected by the strategy."""
- blacklist = InMemoryTokenBlacklist()
- strategy = _make_strategy(blacklist)
+ """Token added to denylist must be rejected by the strategy."""
+ denylist = InMemoryTokenDenylist()
+ strategy = _make_strategy(denylist)
token = strategy._create_access_token("user1", ["user"], ["read"])
@@ -82,10 +82,10 @@ async def test_revoke_then_reject(self):
assert result.status == AuthStatus.INVALID
@pytest.mark.asyncio
- async def test_blacklist_does_not_affect_other_tokens(self):
+ async def test_denylist_does_not_affect_other_tokens(self):
"""Revoking one token must not affect other valid tokens."""
- blacklist = InMemoryTokenBlacklist()
- strategy = _make_strategy(blacklist)
+ denylist = InMemoryTokenDenylist()
+ strategy = _make_strategy(denylist)
token_a = strategy._create_access_token("userA", ["user"], ["read"])
token_b = strategy._create_access_token("userB", ["user"], ["read"])
@@ -99,72 +99,72 @@ async def test_blacklist_does_not_affect_other_tokens(self):
assert result_b.is_authenticated
@pytest.mark.asyncio
- async def test_expired_blacklist_entry_auto_removed(self):
- """Blacklist entries with past expiry are treated as not blacklisted."""
- blacklist = InMemoryTokenBlacklist()
+ async def test_expired_denylist_entry_auto_removed(self):
+ """Denylist entries with past expiry are treated as not denylisted."""
+ denylist = InMemoryTokenDenylist()
token = "some.jwt.token"
past_expiry = int(time.time()) - 10 # already expired
- await blacklist.add_token(token, expires_at=past_expiry)
+ await denylist.add_token(token, expires_at=past_expiry)
- # Should NOT be blacklisted because the entry itself has expired
- assert await blacklist.is_blacklisted(token) is False
+ # Should NOT be denylisted because the entry itself has expired
+ assert await denylist.is_denylisted(token) is False
@pytest.mark.asyncio
async def test_cleanup_removes_only_expired(self):
"""cleanup_expired removes expired entries and keeps valid ones."""
- blacklist = InMemoryTokenBlacklist()
+ denylist = InMemoryTokenDenylist()
- await blacklist.add_token("expired1", int(time.time()) - 5)
- await blacklist.add_token("expired2", int(time.time()) - 1)
- await blacklist.add_token("valid1", int(time.time()) + 3600)
+ await denylist.add_token("expired1", int(time.time()) - 5)
+ await denylist.add_token("expired2", int(time.time()) - 1)
+ await denylist.add_token("valid1", int(time.time()) + 3600)
- removed = await blacklist.cleanup_expired()
+ removed = await denylist.cleanup_expired()
assert removed == 2
- assert await blacklist.is_blacklisted("valid1") is True
- assert await blacklist.get_blacklist_size() == 1
+ assert await denylist.is_denylisted("valid1") is True
+ assert await denylist.get_denylist_size() == 1
@pytest.mark.asyncio
- async def test_remove_token_from_blacklist(self):
- """Explicitly removed token is no longer blacklisted."""
- blacklist = InMemoryTokenBlacklist()
+ async def test_remove_token_from_denylist(self):
+ """Explicitly removed token is no longer denylisted."""
+ denylist = InMemoryTokenDenylist()
token = "removable.token"
- await blacklist.add_token(token)
- assert await blacklist.is_blacklisted(token) is True
+ await denylist.add_token(token)
+ assert await denylist.is_denylisted(token) is True
- removed = await blacklist.remove_token(token)
+ removed = await denylist.remove_token(token)
assert removed is True
- assert await blacklist.is_blacklisted(token) is False
+ assert await denylist.is_denylisted(token) is False
@pytest.mark.asyncio
async def test_remove_nonexistent_token_returns_false(self):
"""Removing a token that was never added returns False."""
- blacklist = InMemoryTokenBlacklist()
- result = await blacklist.remove_token("ghost.token")
+ denylist = InMemoryTokenDenylist()
+ result = await denylist.remove_token("ghost.token")
assert result is False
@pytest.mark.asyncio
- async def test_blacklist_size_tracks_additions(self):
- """get_blacklist_size reflects the number of active entries."""
- blacklist = InMemoryTokenBlacklist()
- assert await blacklist.get_blacklist_size() == 0
+ async def test_denylist_size_tracks_additions(self):
+ """get_denylist_size reflects the number of active entries."""
+ denylist = InMemoryTokenDenylist()
+ assert await denylist.get_denylist_size() == 0
- await blacklist.add_token("t1")
- await blacklist.add_token("t2")
- await blacklist.add_token("t3")
+ await denylist.add_token("t1")
+ await denylist.add_token("t2")
+ await denylist.add_token("t3")
- assert await blacklist.get_blacklist_size() == 3
+ assert await denylist.get_denylist_size() == 3
# ===========================================================================
-# JWT Token Blacklist – Redis (mocked)
+# JWT Token Denylist – Redis (mocked)
# ===========================================================================
-class TestRedisBlacklistIntegration:
- """Integration tests for RedisTokenBlacklist with a mocked Redis client."""
+class TestRedisDenylistIntegration:
+ """Integration tests for RedisTokenDenylist with a mocked Redis client."""
def _make_redis_mock(self) -> MagicMock:
redis = MagicMock()
@@ -172,17 +172,17 @@ def _make_redis_mock(self) -> MagicMock:
redis.set = AsyncMock(return_value=True)
redis.exists = AsyncMock(return_value=1)
redis.delete = AsyncMock(return_value=1)
- redis.keys = AsyncMock(return_value=["token_blacklist:tok1", "token_blacklist:tok2"])
+ redis.keys = AsyncMock(return_value=["token_denylist:tok1", "token_denylist:tok2"])
return redis
@pytest.mark.asyncio
async def test_add_token_with_expiry_calls_setex(self):
"""add_token with expiry uses Redis SETEX."""
redis = self._make_redis_mock()
- blacklist = RedisTokenBlacklist(redis_client=redis)
+ denylist = RedisTokenDenylist(redis_client=redis)
future_expiry = int(time.time()) + 3600
- result = await blacklist.add_token("mytoken", expires_at=future_expiry)
+ result = await denylist.add_token("mytoken", expires_at=future_expiry)
assert result is True
redis.setex.assert_called_once()
@@ -191,56 +191,56 @@ async def test_add_token_with_expiry_calls_setex(self):
async def test_add_token_without_expiry_calls_set(self):
"""add_token without expiry uses Redis SET."""
redis = self._make_redis_mock()
- blacklist = RedisTokenBlacklist(redis_client=redis)
+ denylist = RedisTokenDenylist(redis_client=redis)
- result = await blacklist.add_token("mytoken")
+ result = await denylist.add_token("mytoken")
assert result is True
redis.set.assert_called_once()
@pytest.mark.asyncio
- async def test_is_blacklisted_returns_true_when_key_exists(self):
- """is_blacklisted returns True when Redis key exists."""
+ async def test_is_denylisted_returns_true_when_key_exists(self):
+ """is_denylisted returns True when Redis key exists."""
redis = self._make_redis_mock()
redis.exists = AsyncMock(return_value=1)
- blacklist = RedisTokenBlacklist(redis_client=redis)
+ denylist = RedisTokenDenylist(redis_client=redis)
- assert await blacklist.is_blacklisted("mytoken") is True
+ assert await denylist.is_denylisted("mytoken") is True
@pytest.mark.asyncio
- async def test_is_blacklisted_returns_false_when_key_missing(self):
- """is_blacklisted returns False when Redis key does not exist."""
+ async def test_is_denylisted_returns_false_when_key_missing(self):
+ """is_denylisted returns False when Redis key does not exist."""
redis = self._make_redis_mock()
redis.exists = AsyncMock(return_value=0)
- blacklist = RedisTokenBlacklist(redis_client=redis)
+ denylist = RedisTokenDenylist(redis_client=redis)
- assert await blacklist.is_blacklisted("mytoken") is False
+ assert await denylist.is_denylisted("mytoken") is False
@pytest.mark.asyncio
async def test_redis_error_fails_secure(self):
- """On Redis error, is_blacklisted fails secure (returns True)."""
+ """On Redis error, is_denylisted fails secure (returns True)."""
redis = self._make_redis_mock()
redis.exists = AsyncMock(side_effect=ConnectionError("Redis down"))
- blacklist = RedisTokenBlacklist(redis_client=redis)
+ denylist = RedisTokenDenylist(redis_client=redis)
- # Fail-secure: assume blacklisted on error
- assert await blacklist.is_blacklisted("anytoken") is True
+ # Fail-secure: assume denylisted on error
+ assert await denylist.is_denylisted("anytoken") is True
@pytest.mark.asyncio
async def test_fallback_to_in_memory_when_no_redis(self):
- """Without a Redis client, RedisTokenBlacklist falls back to in-memory."""
- blacklist = RedisTokenBlacklist(redis_client=None)
+ """Without a Redis client, RedisTokenDenylist falls back to in-memory."""
+ denylist = RedisTokenDenylist(redis_client=None)
- await blacklist.add_token("fallback_token")
- assert await blacklist.is_blacklisted("fallback_token") is True
+ await denylist.add_token("fallback_token")
+ assert await denylist.is_denylisted("fallback_token") is True
@pytest.mark.asyncio
- async def test_get_blacklist_size_counts_keys(self):
- """get_blacklist_size counts matching Redis keys."""
+ async def test_get_denylist_size_counts_keys(self):
+ """get_denylist_size counts matching Redis keys."""
redis = self._make_redis_mock()
- blacklist = RedisTokenBlacklist(redis_client=redis)
+ denylist = RedisTokenDenylist(redis_client=redis)
- size = await blacklist.get_blacklist_size()
+ size = await denylist.get_denylist_size()
assert size == 2 # matches the two keys in the mock
@@ -300,10 +300,10 @@ async def test_non_bearer_scheme_fails(self):
assert not result.is_authenticated
@pytest.mark.asyncio
- async def test_blacklisted_token_rejected_via_authenticate(self):
+ async def test_denylisted_token_rejected_via_authenticate(self):
"""authenticate rejects a token that has been revoked."""
- blacklist = InMemoryTokenBlacklist()
- strategy = _make_strategy(blacklist)
+ denylist = InMemoryTokenDenylist()
+ strategy = _make_strategy(denylist)
token = strategy._create_access_token("bob", ["user"], ["read"])
await strategy.revoke_token(token)
@@ -393,11 +393,11 @@ async def test_access_token_cannot_be_used_as_refresh_token(self):
@pytest.mark.asyncio
async def test_revoked_refresh_token_rejected(self):
"""A revoked refresh token cannot be used to get a new access token."""
- blacklist = InMemoryTokenBlacklist()
- strategy = _make_strategy(blacklist)
+ denylist = InMemoryTokenDenylist()
+ strategy = _make_strategy(denylist)
refresh = strategy.create_refresh_token("frank", ["user"], ["read"])
- await blacklist.add_token(refresh)
+ await denylist.add_token(refresh)
result = await strategy.refresh_token(refresh)
@@ -454,10 +454,10 @@ def test_different_ips_tracked_independently(self):
@pytest.mark.asyncio
async def test_rate_limited_ip_gets_failed_auth_result(self):
"""Strategy returns FAILED when IP is rate-limited."""
- blacklist = InMemoryTokenBlacklist()
+ denylist = InMemoryTokenDenylist()
strategy = EnhancedBearerTokenStrategy(
secret_key=SECRET,
- blacklist=blacklist,
+ denylist=denylist,
algorithm="HS256",
enabled=True,
rate_limit_enabled=True,
diff --git a/tests/onaws/__init__.py b/tests/onaws/__init__.py
deleted file mode 100644
index 1b7b680fc..000000000
--- a/tests/onaws/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# AWS-specific tests package
diff --git a/tests/onmoto/__init__.py b/tests/providers/aws/__init__.py
similarity index 100%
rename from tests/onmoto/__init__.py
rename to tests/providers/aws/__init__.py
diff --git a/tests/onmoto/conftest.py b/tests/providers/aws/conftest.py
similarity index 88%
rename from tests/onmoto/conftest.py
rename to tests/providers/aws/conftest.py
index 302fadb54..f83dfeed0 100644
--- a/tests/onmoto/conftest.py
+++ b/tests/providers/aws/conftest.py
@@ -1,4 +1,20 @@
-"""Shared fixtures for moto-based full-pipeline integration tests."""
+"""Provider-level fixtures for the AWS test tree.
+
+Covers both moto (mocked) and live subtrees. The fixtures that call real
+AWS are gated on the --live flag so they are never accidentally executed in
+CI.
+
+This file is the canonical source of:
+ - moto_aws context manager
+ - moto_vpc_resources
+ - orb_config_dir (moto-backed)
+ - Live-test session fixtures (gated by --live)
+
+Individual conftest.py files under moto/ and live/ may import helpers
+defined here or in their own modules but should not duplicate them.
+"""
+
+from __future__ import annotations
import json
import os
@@ -12,7 +28,7 @@
import pytest
from moto import mock_aws
-sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
+sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
from orb.providers.aws.domain.template.aws_template_aggregate import AWSTemplate
from orb.providers.aws.infrastructure.aws_client import AWSClient
@@ -24,26 +40,22 @@
from orb.providers.aws.utilities.aws_operations import AWSOperations
from tests.utilities.reset_singletons import reset_all_singletons
-_PROJECT_ROOT = Path(__file__).parent.parent.parent
+_PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
_CONFIG_SOURCE = _PROJECT_ROOT / "config"
REGION = "eu-west-2"
# ---------------------------------------------------------------------------
-# Moto compatibility patches
+# Moto compatibility patches (applied to all moto/ tests via autouse=True
+# in tests/providers/aws/moto/conftest.py)
# ---------------------------------------------------------------------------
-@pytest.fixture(autouse=True)
-def patch_moto_compat():
- """Patch moto-incompatible behaviours for all onmoto tests.
-
- 1. AWSImageResolutionService.is_resolution_needed -> False
- Prevents SSM path resolution which moto cannot fulfil.
+def make_patch_moto_compat():
+ """Return a context manager that patches moto-incompatible behaviours.
- 2. AWSProvisioningAdapter._provision_via_handlers synthesises instances
- from instance_ids so the orchestration loop sees fulfilled_count > 0.
+ Extracted as a helper so it can be reused from the moto/ conftest.
"""
from orb.providers.aws.infrastructure.adapters.aws_provisioning_adapter import (
AWSProvisioningAdapter,
@@ -57,9 +69,6 @@ def _patched_provision(self, request, template, dry_run=False):
instance_ids = result.get("instance_ids") or result.get("resource_ids", [])
iids = [i for i in instance_ids if i.startswith("i-")]
if iids:
- # Resolve the resource_id (reservation ID) for each instance so that
- # group_by_resource can group them correctly during return/deprovisioning.
- # resource_ids contains the reservation ID(s); use the first one.
resource_ids = result.get("resource_ids", [])
resource_id = next(
(r for r in resource_ids if not r.startswith("i-")),
@@ -81,15 +90,21 @@ def _patched_provision(self, request, template, dry_run=False):
]
return result
- with (
- patch(
- "orb.providers.aws.infrastructure.services.aws_image_resolution_service"
- ".AWSImageResolutionService.is_resolution_needed",
- return_value=False,
- ),
- patch.object(AWSProvisioningAdapter, "_provision_via_handlers", _patched_provision),
- ):
- yield
+ import contextlib
+
+ @contextlib.contextmanager
+ def _ctx():
+ with (
+ patch(
+ "orb.providers.aws.infrastructure.services.aws_image_resolution_service"
+ ".AWSImageResolutionService.is_resolution_needed",
+ return_value=False,
+ ),
+ patch.object(AWSProvisioningAdapter, "_provision_via_handlers", _patched_provision),
+ ):
+ yield
+
+ return _ctx()
# ---------------------------------------------------------------------------
@@ -139,24 +154,19 @@ def moto_vpc_resources(moto_aws):
# ---------------------------------------------------------------------------
-# ORB config directory
+# ORB config directory (moto-backed)
# ---------------------------------------------------------------------------
@pytest.fixture
def orb_config_dir(tmp_path, moto_vpc_resources):
- """Generate a complete ORB config directory pointing at moto VPC resources.
-
- Writes config.json, aws_templates.json, and default_config.json into
- tmp_path/config/, sets ORB_CONFIG_DIR, and returns the config dir path.
- """
+ """Generate a complete ORB config directory pointing at moto VPC resources."""
config_dir = tmp_path / "config"
config_dir.mkdir(parents=True, exist_ok=True)
subnet_ids = moto_vpc_resources["subnet_ids"]
sg_id = moto_vpc_resources["sg_id"]
- # --- config.json ---
config_data = {
"scheduler": {
"type": "hostfactory",
@@ -200,14 +210,11 @@ def orb_config_dir(tmp_path, moto_vpc_resources):
with open(config_dir / "config.json", "w") as f:
json.dump(config_data, f, indent=2)
- # --- aws_templates.json ---
- # Load via the real scheduler pipeline (HF camelCase source → HF wire format)
try:
- from tests.onaws.template_processor import TemplateProcessor
+ from tests.providers.aws.live.template_processor import TemplateProcessor
templates_data = TemplateProcessor.generate_templates_programmatically("hostfactory")
except Exception:
- # Fallback: copy the source file directly if programmatic generation fails
src = _CONFIG_SOURCE / "aws_templates.json"
if src.exists():
shutil.copy2(src, config_dir / "aws_templates.json")
@@ -217,17 +224,14 @@ def orb_config_dir(tmp_path, moto_vpc_resources):
with open(config_dir / "aws_templates.json", "w") as f:
json.dump(templates_data, f, indent=2)
- # --- default_config.json ---
default_src = _CONFIG_SOURCE / "default_config.json"
if default_src.exists():
shutil.copy2(default_src, config_dir / "default_config.json")
- # Point ORB at this config directory
os.environ["ORB_CONFIG_DIR"] = str(config_dir)
yield config_dir
- # Cleanup env var after test
os.environ.pop("ORB_CONFIG_DIR", None)
@@ -237,7 +241,7 @@ def orb_config_dir(tmp_path, moto_vpc_resources):
@pytest.fixture(autouse=True)
-def reset_singletons():
+def reset_singletons_aws():
"""Reset DI container and all singletons before and after each test."""
from orb.infrastructure.di.container import reset_container
@@ -249,7 +253,7 @@ def reset_singletons():
# ---------------------------------------------------------------------------
-# AWS clients
+# AWS clients (moto-backed)
# ---------------------------------------------------------------------------
@@ -266,7 +270,7 @@ def autoscaling_client(moto_aws):
# ---------------------------------------------------------------------------
-# Handler factory helpers (shared with handler-level tests)
+# Handler factory helpers (shared across moto/ and contract/ subtrees)
# ---------------------------------------------------------------------------
@@ -289,9 +293,6 @@ def _make_config_port(prefix: str = ""):
provider_config = MagicMock()
provider_config.provider_defaults = {"aws": provider_defaults}
config_port.get_provider_config.return_value = provider_config
- # Set app_config to None so that accessing .circuit_breaker on it raises
- # AttributeError, causing _get_circuit_breaker_config to use integer defaults
- # instead of a MagicMock (which breaks int comparisons in the circuit breaker)
config_port.app_config = None
return config_port
@@ -386,36 +387,37 @@ def _inject_moto_factory(aws_client: AWSClient, logger, config_port) -> None:
return
lt_manager = _make_launch_template_manager(aws_client, logger)
- aws_ops = AWSOperations(aws_client, logger, cfg_port)
- factory = AWSHandlerFactory(aws_client=aws_client, logger=logger, config=cfg_port)
+ effective_config = cfg_port if config_port is None else config_port
+ aws_ops = AWSOperations(aws_client, logger, effective_config)
+ factory = AWSHandlerFactory(aws_client=aws_client, logger=logger, config=effective_config)
factory._handlers[ProviderApi.ASG.value] = ASGHandler(
aws_client=aws_client,
logger=logger,
aws_ops=aws_ops,
launch_template_manager=lt_manager,
- config_port=cfg_port,
+ config_port=effective_config,
)
factory._handlers[ProviderApi.EC2_FLEET.value] = EC2FleetHandler(
aws_client=aws_client,
logger=logger,
aws_ops=aws_ops,
launch_template_manager=lt_manager,
- config_port=cfg_port,
+ config_port=effective_config,
)
factory._handlers[ProviderApi.RUN_INSTANCES.value] = RunInstancesHandler(
aws_client=aws_client,
logger=logger,
aws_ops=aws_ops,
launch_template_manager=lt_manager,
- config_port=cfg_port,
+ config_port=effective_config,
)
factory._handlers[ProviderApi.SPOT_FLEET.value] = SpotFleetHandler(
aws_client=aws_client,
logger=logger,
aws_ops=aws_ops,
launch_template_manager=lt_manager,
- config_port=cfg_port,
+ config_port=effective_config,
)
strategy._aws_client = aws_client
diff --git a/tests/unit/providers/aws/__init__.py b/tests/providers/aws/live/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/__init__.py
rename to tests/providers/aws/live/__init__.py
diff --git a/tests/onaws/cleanup_helpers.py b/tests/providers/aws/live/cleanup_helpers.py
similarity index 100%
rename from tests/onaws/cleanup_helpers.py
rename to tests/providers/aws/live/cleanup_helpers.py
diff --git a/tests/onaws/conftest.py b/tests/providers/aws/live/conftest.py
similarity index 54%
rename from tests/onaws/conftest.py
rename to tests/providers/aws/live/conftest.py
index 19e3d6169..b09d6d4ac 100644
--- a/tests/onaws/conftest.py
+++ b/tests/providers/aws/live/conftest.py
@@ -1,4 +1,9 @@
-"""onaws integration test configuration."""
+"""Live AWS integration test configuration.
+
+Tests in this subtree require real AWS credentials and a pre-configured ORB
+environment (``orb init``). They are skipped by default; pass ``--live`` (or
+the legacy ``--run-aws``) to enable them.
+"""
import json
import logging
@@ -12,8 +17,22 @@
from boto3 import Session
from botocore.exceptions import ClientError, NoCredentialsError
+
+def _find_repo_root(start: Path) -> Path:
+ """Walk up from *start* until we find pyproject.toml. Hard-fail otherwise.
+
+ Robust against test-tree reshuffles: counting parents (e.g. `parent ** 5`)
+ silently breaks the moment a test file moves up or down the tree. The
+ pyproject.toml marker is stable as long as the repo has one.
+ """
+ for candidate in (start, *start.parents):
+ if (candidate / "pyproject.toml").is_file():
+ return candidate
+ raise RuntimeError(f"Could not locate repo root (no pyproject.toml) above {start}")
+
+
# Ensure repo root is on sys.path so hfmock.py and other root-level modules are importable
-repo_root = Path(__file__).parent.parent.parent
+repo_root = _find_repo_root(Path(__file__).resolve().parent)
if str(repo_root) not in sys.path:
sys.path.insert(0, str(repo_root))
@@ -23,49 +42,50 @@
logs_dir.mkdir(exist_ok=True)
+def _is_live_run(config) -> bool:
+ """Return True when live tests have been explicitly requested."""
+ return config.getoption("--live", default=False) or config.getoption("--run-aws", default=False)
+
+
def _get_aws_profile_and_region() -> tuple[str | None, str | None]:
"""Read profile and region from ORB config.
- Priority:
- 1. ORB_CONFIG_DIR env var (per-test config dir)
- 2. Project config/config.json (written by orb init)
- 3. AWS_REGION / AWS_DEFAULT_REGION env vars
+ Delegates to ORB's own config-discovery (``orb.config.platform_dirs``) so
+ test discovery never drifts from runtime discovery. Falls back to the
+ AWS_REGION / AWS_DEFAULT_REGION env vars for the region only.
"""
- candidates = []
- config_dir = os.environ.get("ORB_CONFIG_DIR")
- if config_dir:
- candidates.append(os.path.join(config_dir, "config.json"))
- # Fall back to the project's real config written by orb init
- candidates.append(str(repo_root / "config" / "config.json"))
-
- for config_path in candidates:
- try:
- with open(config_path) as f:
- config = json.load(f)
- providers = config.get("provider", {}).get("providers", [])
- if providers:
- provider_config = providers[0].get("config", {})
- profile = provider_config.get("profile")
- region = provider_config.get("region")
- if profile or region:
- return profile, region
- except Exception:
- pass
+ from orb.config.platform_dirs import get_config_location
+
+ config_path = get_config_location() / "config.json"
+ try:
+ with open(config_path) as f:
+ config = json.load(f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_config = providers[0].get("config", {})
+ profile = provider_config.get("profile")
+ region = provider_config.get("region")
+ if profile or region:
+ return profile, region
+ except FileNotFoundError:
+ pass
region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
return None, region
def pytest_configure(config) -> None:
- """Pre-flight check: verify orb init has been run."""
- config_dir = os.environ.get("ORB_CONFIG_DIR", ".")
- config_path = Path(config_dir)
+ """Pre-flight check: verify orb init has been run (only when --live is active)."""
+ if not _is_live_run(config):
+ return
+
+ from orb.config.platform_dirs import get_scripts_location
- scripts_dir = config_path / "scripts"
+ scripts_dir = get_scripts_location()
if not scripts_dir.exists():
pytest.exit(
- "onaws pre-flight failed: scripts/ directory not found.\n"
+ "live pre-flight failed: scripts/ directory not found.\n"
"Run 'orb init' first to set up the environment.\n"
f"Looked in: {scripts_dir.resolve()}",
returncode=1,
@@ -74,7 +94,7 @@ def pytest_configure(config) -> None:
invoke_script = scripts_dir / "invoke_provider.sh"
if not invoke_script.exists():
pytest.exit(
- "onaws pre-flight failed: scripts/invoke_provider.sh not found.\n"
+ "live pre-flight failed: scripts/invoke_provider.sh not found.\n"
"Run 'orb init' first to set up the environment.\n"
f"Looked in: {invoke_script.resolve()}",
returncode=1,
@@ -84,19 +104,33 @@ def pytest_configure(config) -> None:
def pytest_sessionstart(session: pytest.Session) -> None:
"""Check AWS credentials once before any AWS tests run.
- Only runs when --run-aws is passed. Calls sts:GetCallerIdentity and exits
- immediately if credentials are invalid so no tests are attempted.
+ Only runs when --live (or --run-aws) is passed. Calls sts:GetCallerIdentity
+ and exits immediately if credentials are invalid so no tests are attempted.
+
+ Also exports AWS_PROFILE and AWS_REGION/AWS_DEFAULT_REGION from the ORB
+ config into the process env so that bare ``boto3.Session()`` instances
+ inside test modules and ORB SDK code pick the right account+region. Any
+ pre-existing AWS_PROFILE / AWS_REGION env vars are overridden — the ORB
+ config is the single source of truth for live test runs.
"""
- if not session.config.getoption("--run-aws", default=False):
+ if not _is_live_run(session.config):
return
profile, region = _get_aws_profile_and_region()
region = region or "eu-west-1"
+
+ # Override caller's shell env so bare boto3.Session() honours config
+ if profile:
+ os.environ["AWS_PROFILE"] = profile
+ os.environ["AWS_REGION"] = region
+ os.environ["AWS_DEFAULT_REGION"] = region
+
try:
boto_session = Session(profile_name=profile, region_name=region)
sts = boto_session.client("sts", region_name=region)
identity = sts.get_caller_identity()
print(
- f"\n✓ AWS credentials valid: {identity.get('Arn')} (account: {identity.get('Account')})"
+ f"\n✓ AWS credentials valid: {identity.get('Arn')} "
+ f"(account: {identity.get('Account')}, region: {region}, profile: {profile!r})"
)
except NoCredentialsError as e:
pytest.exit(f"AWS credentials not found (profile={profile!r}): {e}", returncode=1)
@@ -140,7 +174,7 @@ def nuclear_cleanup(test_session_id: str):
yield
try:
- from tests.onaws.cleanup_helpers import cleanup_all_orb_resources
+ from tests.providers.aws.live.cleanup_helpers import cleanup_all_orb_resources
profile, region = _get_aws_profile_and_region()
region = region or "eu-west-1"
diff --git a/tests/onaws/parse_output.py b/tests/providers/aws/live/parse_output.py
similarity index 100%
rename from tests/onaws/parse_output.py
rename to tests/providers/aws/live/parse_output.py
diff --git a/tests/onaws/plugin_io_schemas.py b/tests/providers/aws/live/plugin_io_schemas.py
similarity index 99%
rename from tests/onaws/plugin_io_schemas.py
rename to tests/providers/aws/live/plugin_io_schemas.py
index 07e430ce8..729fad4d2 100644
--- a/tests/onaws/plugin_io_schemas.py
+++ b/tests/providers/aws/live/plugin_io_schemas.py
@@ -103,7 +103,6 @@
"name",
"result",
"status",
- "privateIpAddress",
"launchtime",
"message",
],
@@ -221,7 +220,6 @@
"name",
"result",
"status",
- "private_ip_address",
"launch_time",
"message",
],
diff --git a/tests/onaws/resource-history-capture.md b/tests/providers/aws/live/resource-history-capture.md
similarity index 100%
rename from tests/onaws/resource-history-capture.md
rename to tests/providers/aws/live/resource-history-capture.md
diff --git a/tests/onaws/scenarios.py b/tests/providers/aws/live/scenarios.py
similarity index 87%
rename from tests/onaws/scenarios.py
rename to tests/providers/aws/live/scenarios.py
index cd34769ae..57908695a 100644
--- a/tests/onaws/scenarios.py
+++ b/tests/providers/aws/live/scenarios.py
@@ -1,9 +1,12 @@
import itertools
import json
+import logging
import os
from pathlib import Path
from typing import Any, Dict, List
+logger = logging.getLogger(__name__)
+
def _get_templates_for_resolution() -> List[Dict[str, Any]]:
"""Get templates for ID resolution using handler classmethods, with filesystem fallback."""
@@ -368,7 +371,7 @@ def generate_scenarios_from_attributes(
# Generate all combinations
for combination in itertools.product(*attribute_values):
# Create the overrides dictionary from the combination
- overrides = dict(zip(attribute_names, combination))
+ overrides = dict(zip(attribute_names, combination, strict=False))
# Generate test name
if naming_template:
@@ -548,3 +551,74 @@ def get_test_cases() -> List[Dict[str, Any]]:
all_scenarios.extend(CUSTOM_TEST_CASES)
return all_scenarios
+
+
+def _load_template_vm_types(template_id: str) -> Dict[str, Any]:
+ """Look up vmTypes for *template_id* from aws_templates.json.
+
+ Returns an empty dict when the file is absent or the template is not found.
+ """
+ templates_path = (
+ Path(__file__).parent.parent.parent.parent.parent / "config" / "aws_templates.json"
+ )
+ if not templates_path.exists():
+ return {}
+ try:
+ with open(templates_path) as f:
+ data = json.load(f)
+ for tpl in data.get("templates", []):
+ if tpl.get("templateId") == template_id:
+ return tpl.get("vmTypes") or {}
+ except (FileNotFoundError, json.JSONDecodeError):
+ return {}
+ except Exception:
+ logger.warning(
+ "_load_template_vm_types: unexpected error reading %s for template %r",
+ templates_path,
+ template_id,
+ exc_info=True,
+ )
+ raise
+ return {}
+
+
+# Provider APIs that do not use capacity-unit weighting even when their template
+# config lists weights. RunInstances always provisions one instance per unit.
+_UNWEIGHTED_PROVIDER_APIS = {"RunInstances"}
+
+
+def template_uses_weighted_capacity(test_case: Dict[str, Any]) -> bool:
+ """Return True if this scenario's effective vmTypes use weights > 1.
+
+ Weighted templates fulfil capacity in *capacity units*, not instance count.
+ AWS EC2Fleet / SpotFleet / ASG all support mixed-instance weighting: when a
+ template lists vmTypes with weights > 1, requested_count is in capacity units
+ and the actual instance count depends on which weights AWS picks.
+
+ Resolution order for vmTypes:
+ 1. ``overrides["vmTypes"]`` — explicit per-scenario override takes precedence.
+ 2. The template's own vmTypes read from ``config/aws_templates.json``.
+
+ RunInstances is always unweighted: the API provisions exactly one instance per
+ requested unit regardless of any weight values in the template config.
+ """
+ overrides = test_case.get("overrides", {}) or {}
+
+ # RunInstances never uses capacity-unit weighting.
+ provider_api = overrides.get("providerApi") or ""
+ if provider_api in _UNWEIGHTED_PROVIDER_APIS:
+ return False
+
+ # 1. Use explicit override if present.
+ vm_types: Dict[str, Any] | None = overrides.get("vmTypes") or overrides.get("vm_types")
+
+ # 2. Fall back to the template's own vmTypes.
+ if not vm_types:
+ template_id = test_case.get("template_id") or ""
+ if template_id:
+ vm_types = _load_template_vm_types(template_id) or None
+
+ if not vm_types:
+ return False
+
+ return any(int(w) > 1 for w in vm_types.values())
diff --git a/tests/onaws/scenarios_mcp.py b/tests/providers/aws/live/scenarios_mcp.py
similarity index 100%
rename from tests/onaws/scenarios_mcp.py
rename to tests/providers/aws/live/scenarios_mcp.py
diff --git a/tests/onaws/scenarios_rest_api.py b/tests/providers/aws/live/scenarios_rest_api.py
similarity index 98%
rename from tests/onaws/scenarios_rest_api.py
rename to tests/providers/aws/live/scenarios_rest_api.py
index 2460a5426..095da43d8 100644
--- a/tests/onaws/scenarios_rest_api.py
+++ b/tests/providers/aws/live/scenarios_rest_api.py
@@ -3,7 +3,7 @@
import itertools
from typing import Any, Dict, List, Optional
-from tests.onaws.scenarios import (
+from tests.providers.aws.live.scenarios import (
CUSTOM_TEST_CASES as CUSTOM_TEST_CASES,
DEFAULT_ATTRIBUTE_COMBINATIONS as _DEFAULT_ATTRIBUTE_COMBINATIONS,
resolve_template_id,
@@ -56,7 +56,7 @@
# Centralized timeouts/constants for REST API tests
REST_API_TIMEOUTS = {
- "rest_api_timeout": 60, # Per-request HTTP timeout for REST calls
+ "rest_api_timeout": 120, # Per-request HTTP timeout for REST calls (large ABIS scenarios may exceed 60s)
"rest_api_retry_attempts": 3, # Retry attempts for REST calls (when implemented)
"server_start": 30, # How long to wait for the server to start responding to health
"health_check": 5, # Health endpoint timeout
diff --git a/tests/onaws/scenarios_sdk.py b/tests/providers/aws/live/scenarios_sdk.py
similarity index 100%
rename from tests/onaws/scenarios_sdk.py
rename to tests/providers/aws/live/scenarios_sdk.py
diff --git a/tests/onaws/template_processor.py b/tests/providers/aws/live/template_processor.py
similarity index 95%
rename from tests/onaws/template_processor.py
rename to tests/providers/aws/live/template_processor.py
index 0c09a89e9..73a86a282 100644
--- a/tests/onaws/template_processor.py
+++ b/tests/providers/aws/live/template_processor.py
@@ -9,8 +9,8 @@
config/aws_templates.json ──load_templates_from_path──► raw dicts (with real image_id)
──format_templates_for_generation──► scheduler wire format
──copy + override──► run_templates//aws_templates.json
- config/config.json ──merge overrides──► run_templates//config.json
- config/default_config.json ──copy──────────────► run_templates//default_config.json
+ config/config.json ──merge overrides──► run_templates//config/config.json
+ config/default_config.json ──copy──────────────► run_templates//config/default_config.json
"""
import json
@@ -91,18 +91,26 @@ def _normalize_key(key: str, target_fmt: str) -> str:
return _SNAKE_TO_CAMEL.get(key, key)
+def _find_repo_root(start: Path) -> Path:
+ """Walk up from *start* until pyproject.toml is found. Hard-fail otherwise."""
+ for candidate in (start, *start.parents):
+ if (candidate / "pyproject.toml").is_file():
+ return candidate
+ raise RuntimeError(f"Could not locate repo root (no pyproject.toml) above {start}")
+
+
class TemplateProcessor:
"""Generates per-test config directories from the real project config files."""
def __init__(self, base_dir: str | None = None):
resolved: Path
if base_dir is None:
- resolved = Path(__file__).parent
+ resolved = Path(__file__).resolve().parent
else:
resolved = Path(base_dir)
self.base_dir = resolved
- self.config_source_dir = resolved.parent.parent / "config"
+ self.config_source_dir = _find_repo_root(resolved) / "config"
self.run_templates_dir = resolved / "run_templates"
# ------------------------------------------------------------------
@@ -257,10 +265,11 @@ def generate_templates_programmatically(scheduler_type: str = "hostfactory") ->
"""
import sys
- sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
+ repo_root = _find_repo_root(Path(__file__).resolve().parent)
+ sys.path.insert(0, str(repo_root / "src"))
# Find the real generated templates file in config/
- config_dir = Path(__file__).parent.parent.parent / "config"
+ config_dir = repo_root / "config"
templates_file = config_dir / "aws_templates.json"
if not templates_file.exists():
raise FileNotFoundError(
diff --git a/tests/onaws/test_cleanup_e2e_onaws.py b/tests/providers/aws/live/test_cleanup_e2e_onaws.py
similarity index 71%
rename from tests/onaws/test_cleanup_e2e_onaws.py
rename to tests/providers/aws/live/test_cleanup_e2e_onaws.py
index f859f84fa..6c68a6074 100644
--- a/tests/onaws/test_cleanup_e2e_onaws.py
+++ b/tests/providers/aws/live/test_cleanup_e2e_onaws.py
@@ -13,23 +13,24 @@
import boto3.session
import pytest
+from botocore.exceptions import ClientError
-from tests.onaws import scenarios
-from tests.onaws.cleanup_helpers import (
+from tests.providers.aws.live import scenarios
+from tests.providers.aws.live.cleanup_helpers import (
cleanup_launch_templates_for_request,
get_machine_ids_from_ec2 as _get_machine_ids_from_ec2_helper,
wait_for_instances_terminated,
)
-from tests.onaws.scenarios import SPOT_VM_TYPES
-from tests.onaws.template_processor import TemplateProcessor
+from tests.providers.aws.live.scenarios import SPOT_VM_TYPES
+from tests.providers.aws.live.template_processor import TemplateProcessor
try:
- from tests.onaws.scenarios_sdk import SDK_TIMEOUTS # type: ignore[import]
+ from tests.providers.aws.live.scenarios_sdk import SDK_TIMEOUTS # type: ignore[import]
except ImportError:
SDK_TIMEOUTS = {"request_completion": 600, "return_completion": 300, "poll_interval": 5}
try:
- from tests.onaws.test_onaws import (
+ from tests.providers.aws.live.test_onaws import (
_check_all_ec2_hosts_are_being_terminated,
_get_resource_id_from_instance,
get_instance_state,
@@ -52,15 +53,71 @@
os.environ["LOG_DESTINATION"] = "file"
os.environ.setdefault("AWS_REGION", "eu-west-1")
-_boto_session = boto3.session.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
-asg_client = _boto_session.client("autoscaling", region_name=_ec2_region)
+_ec2_client = None
+_asg_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config.
+
+ Falls back to AWS_PROFILE env var so that an explicit profile is always
+ passed to boto3. This is necessary because pytest-env injects fake env
+ var credentials (AWS_ACCESS_KEY_ID=testing) for unit tests; botocore uses
+ env var credentials before profile credentials, so any session created
+ without an explicit profile_name ends up using the fake credentials and
+ gets AuthFailure from real AWS calls. Passing an explicit profile_name
+ forces botocore into profile-based credential resolution, bypassing the
+ injected env vars.
+ """
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ # Fall back to AWS_PROFILE env var so the explicit profile_name is always
+ # non-None when a profile is available. This forces botocore to use
+ # profile-based credentials and ignore injected fake env var credentials.
+ profile = profile or os.environ.get("AWS_PROFILE")
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.session.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
+
+def _get_asg_client():
+ global _asg_client
+ if _asg_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _asg_client = boto3.session.Session(profile_name=_profile, region_name=_region).client(
+ "autoscaling", region_name=_region
+ )
+ return _asg_client
+
log = logging.getLogger("cleanup_e2e_test")
log.setLevel(logging.DEBUG)
@@ -159,13 +216,34 @@
# ---------------------------------------------------------------------------
-def _assert_asg_deleted(asg_name: str, timeout: int = 120) -> None:
- """Assert ASG is deleted or has zero desired capacity with no instances."""
+def _assert_asg_deleted(asg_name: str, timeout: int = 300, weighted: bool = False) -> None:
+ """Assert ASG is deleted or has zero desired capacity with no active instances.
+
+ Args:
+ asg_name: Name of the Auto Scaling Group to check.
+ timeout: Maximum seconds to wait.
+ weighted: When True (weighted-capacity template), the lenient condition is used:
+ no active instances is sufficient even if DesiredCapacity > 0, because
+ ``ShouldDecrementDesiredCapacity=False`` decrements by instance count rather
+ than by capacity weight, so the counter may not reach zero.
+ When False (unweighted template), the strict condition is required:
+ DesiredCapacity must be 0 AND there must be no active instances.
+ """
deadline = time.time() + timeout
last_state = None
+ _terminal_lifecycle = frozenset(
+ {
+ "Terminating",
+ "Terminating:Wait",
+ "Terminating:Proceed",
+ "Terminated",
+ "Detaching",
+ "Detached",
+ }
+ )
while time.time() < deadline:
try:
- resp = asg_client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])
+ resp = _get_asg_client().describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])
groups = resp.get("AutoScalingGroups", [])
if not groups:
log.info("ASG %s: not found (deleted)", asg_name)
@@ -173,25 +251,42 @@ def _assert_asg_deleted(asg_name: str, timeout: int = 120) -> None:
g = groups[0]
desired = g.get("DesiredCapacity", -1)
instances = g.get("Instances", [])
- last_state = f"DesiredCapacity={desired}, Instances={len(instances)}"
- if desired == 0 and not instances:
- log.info("ASG %s: DesiredCapacity=0 with no instances", asg_name)
+ active_instances = [
+ i for i in instances if i.get("LifecycleState", "") not in _terminal_lifecycle
+ ]
+ last_state = (
+ f"DesiredCapacity={desired}, Instances={len(instances)}, "
+ f"ActiveInstances={len(active_instances)}"
+ )
+ if weighted:
+ # Lenient check for weighted templates: no active instances is sufficient.
+ # ShouldDecrementDesiredCapacity decrements by instance count, not weight,
+ # so DesiredCapacity may remain > 0 after all instances are gone.
+ passed = not active_instances
+ else:
+ # Strict check for unweighted templates: DesiredCapacity must reach 0.
+ passed = desired == 0 and not active_instances
+ if passed:
+ log.info("ASG %s: no active instances (DesiredCapacity=%s)", asg_name, desired)
return
log.debug("ASG %s: %s — waiting", asg_name, last_state)
except Exception as exc:
+ # Surface the exception type so it appears verbatim in test output
+ # instead of the opaque "Last state: None" message.
+ last_state = f"describe_error={type(exc).__name__}: {exc}"
log.warning("_assert_asg_deleted: describe failed for %s: %s", asg_name, exc)
time.sleep(10)
pytest.fail(f"ASG {asg_name} not deleted or zeroed within {timeout}s. Last state: {last_state}")
-def _assert_fleet_deleted(fleet_id: str, timeout: int = 120) -> None:
+def _assert_fleet_deleted(fleet_id: str, timeout: int = 300) -> None:
"""Assert EC2 Fleet is deleted or has zero total target capacity."""
deadline = time.time() + timeout
last_state = None
- deleted_states = {"deleted", "deleted-running", "deleted-terminating"}
+ deleted_states = {"deleted", "deleted_running", "deleted_terminating"}
while time.time() < deadline:
try:
- resp = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ resp = _get_ec2_client().describe_fleets(FleetIds=[fleet_id])
fleets = resp.get("Fleets", [])
if not fleets:
log.info("EC2 Fleet %s: not found (deleted)", fleet_id)
@@ -208,6 +303,7 @@ def _assert_fleet_deleted(fleet_id: str, timeout: int = 120) -> None:
if "InvalidFleetId" in str(exc) or "NotFound" in str(exc):
log.info("EC2 Fleet %s: not found (deleted)", fleet_id)
return
+ last_state = f"describe_error={type(exc).__name__}: {exc}"
log.warning("_assert_fleet_deleted: describe failed for %s: %s", fleet_id, exc)
time.sleep(10)
pytest.fail(
@@ -215,14 +311,14 @@ def _assert_fleet_deleted(fleet_id: str, timeout: int = 120) -> None:
)
-def _assert_spot_fleet_deleted(sfr_id: str, timeout: int = 120) -> None:
+def _assert_spot_fleet_deleted(sfr_id: str, timeout: int = 300) -> None:
"""Assert Spot Fleet request is cancelled/deleted or has zero target capacity."""
deadline = time.time() + timeout
last_state = None
terminal_states = {"cancelled", "cancelled_running", "cancelled_terminating", "failed"}
while time.time() < deadline:
try:
- resp = ec2_client.describe_spot_fleet_requests(SpotFleetRequestIds=[sfr_id])
+ resp = _get_ec2_client().describe_spot_fleet_requests(SpotFleetRequestIds=[sfr_id])
configs = resp.get("SpotFleetRequestConfigs", [])
if not configs:
log.info("Spot Fleet %s: not found (deleted)", sfr_id)
@@ -239,6 +335,7 @@ def _assert_spot_fleet_deleted(sfr_id: str, timeout: int = 120) -> None:
if "NotFound" in str(exc) or "InvalidSpotFleetRequestId" in str(exc):
log.info("Spot Fleet %s: not found (deleted)", sfr_id)
return
+ last_state = f"describe_error={type(exc).__name__}: {exc}"
log.warning("_assert_spot_fleet_deleted: describe failed for %s: %s", sfr_id, exc)
time.sleep(10)
pytest.fail(
@@ -249,7 +346,7 @@ def _assert_spot_fleet_deleted(sfr_id: str, timeout: int = 120) -> None:
def _assert_launch_templates_deleted(request_id: str) -> None:
"""Assert no launch templates tagged orb:request-id= exist."""
try:
- resp = ec2_client.describe_launch_templates(
+ resp = _get_ec2_client().describe_launch_templates(
Filters=[{"Name": "tag:orb:request-id", "Values": [request_id]}]
)
templates = resp.get("LaunchTemplates", [])
@@ -285,7 +382,7 @@ def _extract_resource_ids(result) -> list[str]:
def _get_machine_ids_from_ec2(request_id: str) -> list[str]:
- return _get_machine_ids_from_ec2_helper(request_id, ec2_client)
+ return _get_machine_ids_from_ec2_helper(request_id, _get_ec2_client())
# ---------------------------------------------------------------------------
@@ -316,15 +413,16 @@ def setup_cleanup_e2e(request, test_session_id):
(test_config_dir / "logs").mkdir(exist_ok=True)
(test_config_dir / "work").mkdir(exist_ok=True)
- os.environ["ORB_CONFIG_DIR"] = str(test_config_dir)
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["DEFAULT_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
os.environ["HF_LOGDIR"] = str(test_config_dir / "logs")
- config_path = str(test_config_dir / "config.json")
+ config_path = str(config_dir / "config.json")
_tracked_request_ids: list[str] = []
@@ -377,18 +475,29 @@ async def _cleanup() -> None:
all_machine_ids = [
mid for req_id in _tracked_request_ids for mid in _get_machine_ids_from_ec2(req_id)
]
- wait_for_instances_terminated(all_machine_ids, ec2_client)
+ wait_for_instances_terminated(all_machine_ids, _get_ec2_client())
except Exception as exc:
log.warning("Fixture teardown: wait_for_instances_terminated failed: %s", exc)
for req_id in _tracked_request_ids:
try:
- cleanup_launch_templates_for_request(req_id, ec2_client)
+ cleanup_launch_templates_for_request(req_id, _get_ec2_client())
except Exception as exc:
log.warning(
"Fixture teardown: cleanup_launch_templates failed for %s: %s", req_id, exc
)
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "DEFAULT_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "HF_LOGDIR",
+ ):
+ os.environ.pop(key, None)
+
try:
from orb.infrastructure.di import reset_container
@@ -455,10 +564,29 @@ async def _run_cleanup_verification(
# 3. Assert instances provisioned
machine_ids = _extract_machine_ids(status_response)
- assert len(machine_ids) == capacity, (
- f"Expected {capacity} machines, got {len(machine_ids)}: {machine_ids}"
+
+ # Capacity-unit fulfilment check from provider response fields.
+ # target_units / fulfilled_units reflect capacity units for weighted fleets
+ # and instance count for unweighted templates (1 unit == 1 instance).
+ _req0 = status_response.get("requests", [{}])[0] if isinstance(status_response, dict) else {}
+ target_units = _req0["target_units"] if _req0.get("target_units") is not None else capacity
+ fulfilled_units = (
+ _req0["fulfilled_units"] if _req0.get("fulfilled_units") is not None else len(machine_ids)
+ )
+ assert fulfilled_units >= target_units, (
+ f"Fleet not fully fulfilled: fulfilled={fulfilled_units}, target={target_units}"
)
+ # Template-aware instance count sanity check.
+ if scenarios.template_uses_weighted_capacity(test_case):
+ assert len(machine_ids) >= 1, (
+ f"Expected at least 1 machine (weighted template), got: {machine_ids}"
+ )
+ else:
+ assert len(machine_ids) == capacity, (
+ f"Expected {capacity} machines (unweighted template), got {len(machine_ids)}: {machine_ids}"
+ )
+
returned_id = status_response.get("requests", [{}])[0].get("request_id") or status_response.get(
"requests", [{}]
)[0].get("requestId")
@@ -489,33 +617,27 @@ async def _run_cleanup_verification(
return_request_id = _extract_request_id(return_result)
- # 6. Poll return completion
+ # 6. Poll return completion via get_request_status(return_request_id).
+ # The HostFactory getReturnRequests wire op is for provider-initiated
+ # retirements (preempted/deleted machines), not user-initiated returns —
+ # `get_request_status(return_id)` is the correct API for "is this return
+ # complete?" under any scheduler.
if return_request_id:
deadline = time.time() + SDK_TIMEOUTS["return_completion"]
+ terminal = {"complete", "complete_with_error", "failed", "cancelled", "timeout"}
while True:
- ret_status = await sdk.list_return_requests() # type: ignore[attr-defined]
- requests = (
- ret_status.get("requests", []) if isinstance(ret_status, dict) else ret_status or []
- )
- done = False
- for req in requests:
- rid = (
- req.get("requestId") or req.get("request_id")
- if isinstance(req, dict)
- else getattr(req, "request_id", None)
- )
- s = req.get("status") if isinstance(req, dict) else getattr(req, "status", None)
- if rid == return_request_id and s == "complete":
- done = True
- break
- if done:
+ status_response = await sdk.get_request_status(request_id=return_request_id) # type: ignore[attr-defined]
+ status = _extract_request_status(status_response)
+ if status in terminal:
+ if status != "complete":
+ pytest.fail(f"Return request ended with non-success status: {status}")
break
if time.time() > deadline:
pytest.fail("Timed out waiting for return request to complete")
await asyncio.sleep(SDK_TIMEOUTS["poll_interval"])
# 7. Wait for instance termination
- wait_for_instances_terminated(machine_ids, ec2_client, timeout=300)
+ wait_for_instances_terminated(machine_ids, _get_ec2_client(), timeout=300)
# 8. Assert all instances terminated
all_terminated = _check_all_ec2_hosts_are_being_terminated(machine_ids)
@@ -528,7 +650,10 @@ async def _run_cleanup_verification(
# 9. Assert backing resource deleted / zeroed
if resource_id:
if provider_api == "ASG":
- _assert_asg_deleted(resource_id)
+ _assert_asg_deleted(
+ resource_id,
+ weighted=scenarios.template_uses_weighted_capacity(test_case),
+ )
elif provider_api == "EC2Fleet":
_assert_fleet_deleted(resource_id)
elif provider_api == "SpotFleet":
@@ -652,53 +777,46 @@ async def test_run_instances_cleanup(self, setup_cleanup_e2e):
f"Expected {capacity} machines, got {len(machine_ids)}: {machine_ids}"
)
for machine_id in machine_ids:
- state = get_instance_state(machine_id)
- assert state["exists"], f"Instance {machine_id} not found in AWS"
- assert state["state"] in ("running", "pending"), (
- f"Instance {machine_id} in unexpected state: {state['state']}"
- )
+ try:
+ resp = _get_ec2_client().describe_instances(InstanceIds=[machine_id])
+ inst_state = resp["Reservations"][0]["Instances"][0]["State"]["Name"]
+ assert inst_state in ("running", "pending"), (
+ f"Instance {machine_id} in unexpected state: {inst_state}"
+ )
+ except ClientError as exc:
+ if exc.response["Error"]["Code"] == "InvalidInstanceID.NotFound":
+ pytest.fail(f"Instance {machine_id} not found in AWS")
+ raise
log.info("All %d RunInstances instances provisioned: %s", capacity, machine_ids)
# 4. Return ALL machines
return_result = await sdk.create_return_request(machine_ids=machine_ids) # type: ignore[attr-defined]
return_request_id = _extract_request_id(return_result)
- # 5. Poll return completion
+ # 5. Poll return completion via get_request_status(return_request_id).
+ # See note above: list_return_requests maps to HF getReturnRequests
+ # wire op which is for provider-initiated retirements.
if return_request_id:
deadline = time.time() + SDK_TIMEOUTS["return_completion"]
+ terminal = {"complete", "complete_with_error", "failed", "cancelled", "timeout"}
while True:
- ret_status = await sdk.list_return_requests() # type: ignore[attr-defined]
- requests = (
- ret_status.get("requests", [])
- if isinstance(ret_status, dict)
- else ret_status or []
+ status_response = await sdk.get_request_status( # type: ignore[attr-defined]
+ request_id=return_request_id
)
- done = False
- for req in requests:
- rid = (
- req.get("requestId") or req.get("request_id")
- if isinstance(req, dict)
- else getattr(req, "request_id", None)
- )
- s = (
- req.get("status")
- if isinstance(req, dict)
- else getattr(req, "status", None)
- )
- if rid == return_request_id and s == "complete":
- done = True
- break
- if done:
+ status = _extract_request_status(status_response)
+ if status in terminal:
+ if status != "complete":
+ pytest.fail(f"Return request ended with non-success status: {status}")
break
if time.time() > deadline:
pytest.fail("Timed out waiting for RunInstances return request to complete")
await asyncio.sleep(SDK_TIMEOUTS["poll_interval"])
# 6. Wait for instance termination
- wait_for_instances_terminated(machine_ids, ec2_client, timeout=300)
+ wait_for_instances_terminated(machine_ids, _get_ec2_client(), timeout=300)
# 7. Assert no instances remain in running state via describe_instances
- resp = ec2_client.describe_instances(
+ resp = _get_ec2_client().describe_instances(
InstanceIds=machine_ids,
Filters=[{"Name": "instance-state-name", "Values": ["running", "pending"]}],
)
diff --git a/tests/onaws/test_mcp_onaws.py b/tests/providers/aws/live/test_mcp_onaws.py
similarity index 77%
rename from tests/onaws/test_mcp_onaws.py
rename to tests/providers/aws/live/test_mcp_onaws.py
index 32e342d9a..80b7f93c3 100644
--- a/tests/onaws/test_mcp_onaws.py
+++ b/tests/providers/aws/live/test_mcp_onaws.py
@@ -13,16 +13,16 @@
import boto3.session
import pytest
-from tests.onaws import scenarios
-from tests.onaws.cleanup_helpers import (
+from tests.providers.aws.live import scenarios
+from tests.providers.aws.live.cleanup_helpers import (
cleanup_launch_templates_for_request,
get_machine_ids_from_ec2 as _get_machine_ids_from_ec2_helper,
wait_for_instances_terminated,
)
-from tests.onaws.template_processor import TemplateProcessor
+from tests.providers.aws.live.template_processor import TemplateProcessor
try:
- from tests.onaws.scenarios_mcp import ( # type: ignore[import]
+ from tests.providers.aws.live.scenarios_mcp import ( # type: ignore[import]
MCP_RUN_DEFAULT_COMBINATIONS,
MCP_TIMEOUTS,
)
@@ -32,7 +32,7 @@
# Import AWS validation helpers from test_onaws (guarded for env/creds issues)
try:
- from tests.onaws.test_onaws import (
+ from tests.providers.aws.live.test_onaws import (
_check_all_ec2_hosts_are_being_terminated,
get_instance_state,
)
@@ -54,14 +54,46 @@
os.environ["LOG_DESTINATION"] = "file"
os.environ.setdefault("AWS_REGION", "eu-west-1")
-_boto_session = boto3.session.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
+_ec2_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.session.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
log = logging.getLogger("mcp_test")
log.setLevel(logging.DEBUG)
@@ -83,6 +115,11 @@
@pytest.fixture
def setup_mcp_test(request, test_session_id):
"""Generate per-test config dir, set env vars, construct MCP server, yield, teardown."""
+ import asyncio
+
+ from orb.bootstrap import Application
+ from orb.infrastructure.di import reset_container
+ from orb.infrastructure.di.container import get_container
from orb.interface.mcp.server.core import OpenResourceBrokerMCPServer
processor = TemplateProcessor()
@@ -106,15 +143,24 @@ def setup_mcp_test(request, test_session_id):
(test_config_dir / "logs").mkdir(exist_ok=True)
(test_config_dir / "work").mkdir(exist_ok=True)
- os.environ["ORB_CONFIG_DIR"] = str(test_config_dir)
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["DEFAULT_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
os.environ["HF_LOGDIR"] = str(test_config_dir / "logs")
- mcp_server = OpenResourceBrokerMCPServer(app=None)
+ # Bootstrap ORB so the DI container has providers, CQRS handlers,
+ # configuration etc. registered before MCP tool handlers run. Without
+ # this every MCP request fails with "No strategy found for provider".
+ reset_container()
+ app = Application(config_path=str(config_dir / "config.json"), skip_validation=True)
+ if not asyncio.run(app.initialize()):
+ raise RuntimeError("Failed to initialize ORB application for MCP test")
+
+ mcp_server = OpenResourceBrokerMCPServer(app=get_container())
_tracked_request_ids: list[str] = []
@@ -169,20 +215,31 @@ async def _cleanup() -> None:
for req_id in _tracked_request_ids
for mid in _get_machine_ids_from_ec2(req_id)
],
- ec2_client,
+ _get_ec2_client(),
)
except Exception as exc:
log.warning("Fixture teardown: wait_for_instances_terminated failed: %s", exc)
for req_id in _tracked_request_ids:
try:
- cleanup_launch_templates_for_request(req_id, ec2_client)
+ cleanup_launch_templates_for_request(req_id, _get_ec2_client())
except Exception as exc:
log.warning(
"Fixture teardown: cleanup_launch_templates failed for %s: %s", req_id, exc
)
- # Teardown: reset DI container so next test gets a fresh one
+ # Teardown: clean up env vars and reset DI container so next test gets a fresh one
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "DEFAULT_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "HF_LOGDIR",
+ ):
+ os.environ.pop(key, None)
+
try:
from orb.infrastructure.di import reset_container
@@ -199,7 +256,7 @@ async def _cleanup() -> None:
def _get_machine_ids_from_ec2(request_id: str) -> list[str]:
- return _get_machine_ids_from_ec2_helper(request_id, ec2_client)
+ return _get_machine_ids_from_ec2_helper(request_id, _get_ec2_client())
async def _call_tool(mcp_server, tool_name: str, arguments: dict, msg_id: int = 1) -> dict:
@@ -318,17 +375,36 @@ async def _run_full_cycle_mcp(mcp_server, test_case: dict, tracked_request_ids:
)
machine_ids = _extract_machine_ids(status_result)
- assert len(machine_ids) == capacity, (
- f"Expected {capacity} machine(s), got {len(machine_ids)}: {machine_ids}"
+
+ # Capacity-unit fulfilment check from provider response fields.
+ # target_units / fulfilled_units reflect capacity units for weighted fleets
+ # and instance count for unweighted templates (1 unit == 1 instance).
+ _req0 = status_result.get("requests", [{}])[0] if isinstance(status_result, dict) else {}
+ target_units = _req0["target_units"] if _req0.get("target_units") is not None else capacity
+ fulfilled_units = (
+ _req0["fulfilled_units"] if _req0.get("fulfilled_units") is not None else len(machine_ids)
+ )
+ assert fulfilled_units >= target_units, (
+ f"Fleet not fully fulfilled: fulfilled={fulfilled_units}, target={target_units}"
)
+ # Template-aware instance count sanity check.
+ if scenarios.template_uses_weighted_capacity(test_case):
+ assert len(machine_ids) >= 1, (
+ f"Expected at least 1 machine (weighted template), got: {machine_ids}"
+ )
+ else:
+ assert len(machine_ids) == capacity, (
+ f"Expected {capacity} machines (unweighted template), got {len(machine_ids)}: {machine_ids}"
+ )
+
for machine_id in machine_ids:
state = get_instance_state(machine_id)
assert state["exists"], f"Instance {machine_id} not found in AWS"
assert state["state"] in ("running", "pending"), (
f"Instance {machine_id} in unexpected state: {state['state']}"
)
- log.info("All %d instance(s) provisioned: %s", capacity, machine_ids)
+ log.info("All %d instance(s) provisioned: %s", len(machine_ids), machine_ids)
# 4. Return machines
return_result = await _call_tool(mcp_server, "return_machines", {"machine_ids": machine_ids})
@@ -336,19 +412,21 @@ async def _run_full_cycle_mcp(mcp_server, test_case: dict, tracked_request_ids:
return_request_id = _extract_request_id(return_result)
- # 5. Poll return completion via list_return_requests
+ # 5. Poll return completion via get_request_status(return_request_id).
+ # The HostFactory getReturnRequests wire format flattens per-machine items
+ # without a per-request status, so the right way to ask "is this return
+ # complete?" under any scheduler is get_request_status(return_id).
if return_request_id:
deadline = time.time() + MCP_TIMEOUTS["return_completion"]
+ terminal = {"complete", "complete_with_error", "failed", "cancelled", "timeout"}
while True:
- list_result = await _call_tool(mcp_server, "list_return_requests", {})
- requests = list_result.get("requests", [])
- done = any(
- (req.get("requestId") or req.get("request_id")) == return_request_id
- and req.get("status") == "complete"
- for req in requests
- if isinstance(req, dict)
+ status_resp = await _call_tool(
+ mcp_server, "get_request_status", {"request_id": return_request_id}
)
- if done:
+ status = _extract_request_status(status_resp)
+ if status in terminal:
+ if status != "complete":
+ pytest.fail(f"Return request ended with non-success status: {status}")
break
if time.time() > deadline:
pytest.fail("Timed out waiting for return request to complete")
diff --git a/tests/onaws/test_multi_asg_termination.py b/tests/providers/aws/live/test_multi_asg_termination.py
similarity index 86%
rename from tests/onaws/test_multi_asg_termination.py
rename to tests/providers/aws/live/test_multi_asg_termination.py
index 6eb26a418..2d25e2a43 100644
--- a/tests/onaws/test_multi_asg_termination.py
+++ b/tests/providers/aws/live/test_multi_asg_termination.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import re
import time
import uuid
from pathlib import Path
@@ -12,11 +13,11 @@
from jsonschema import ValidationError, validate as validate_json_schema
from hfmock import HostFactoryMock
-from tests.onaws import plugin_io_schemas
-from tests.onaws.cleanup_helpers import cleanup_tracked_requests
-from tests.onaws.parse_output import parse_and_print_output
-from tests.onaws.template_processor import TemplateProcessor
-from tests.onaws.test_onaws import get_instances_states
+from tests.providers.aws.live import plugin_io_schemas
+from tests.providers.aws.live.cleanup_helpers import cleanup_tracked_requests
+from tests.providers.aws.live.parse_output import parse_and_print_output
+from tests.providers.aws.live.template_processor import TemplateProcessor
+from tests.providers.aws.live.test_onaws import get_instances_states
pytestmark = [ # Apply default markers to every test in this module
pytest.mark.manual_aws,
@@ -31,15 +32,61 @@
os.environ["AWS_PROVIDER_LOG_DIR"] = "./logs"
os.environ["LOG_DESTINATION"] = "file"
-_boto_session = boto3.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
-autoscaling_client = _boto_session.client("autoscaling", region_name=_ec2_region)
+_ec2_client = None
+_autoscaling_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config, same as test_onaws._get_boto_clients."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ # Fall back to AWS_PROFILE env var so the explicit profile_name is always
+ # non-None when a profile is available. This forces botocore to use
+ # profile-based credentials and ignore injected fake env var credentials.
+ profile = profile or os.environ.get("AWS_PROFILE")
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
+
+def _get_autoscaling_client():
+ global _autoscaling_client
+ if _autoscaling_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _autoscaling_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "autoscaling", region_name=_region
+ )
+ return _autoscaling_client
+
log = logging.getLogger("multi_asg_test")
log.setLevel(logging.DEBUG)
@@ -52,12 +99,15 @@
log.addHandler(console_handler)
MAX_TIME_WAIT_FOR_CAPACITY_PROVISIONING_SEC = 180
+_IPV4_RE = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
def get_asg_instances(asg_name: str) -> List[str]:
"""Get all instance IDs from an ASG."""
try:
- response = autoscaling_client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])
+ response = _get_autoscaling_client().describe_auto_scaling_groups(
+ AutoScalingGroupNames=[asg_name]
+ )
if not response["AutoScalingGroups"]:
return []
@@ -74,7 +124,9 @@ def verify_asg_instances_detached(instance_ids: List[str]) -> bool:
if not instance_ids:
return True
- response = autoscaling_client.describe_auto_scaling_instances(InstanceIds=instance_ids)
+ response = _get_autoscaling_client().describe_auto_scaling_instances(
+ InstanceIds=instance_ids
+ )
# If any instances are still in ASGs, they will appear in the response
remaining_asg_instances = response.get("AutoScalingInstances", [])
@@ -103,7 +155,7 @@ def verify_asg_instances_detached(instance_ids: List[str]) -> bool:
def check_all_instances_terminating_or_terminated(instance_ids: List[str]) -> bool:
"""Check if all instances are in terminating or terminated state."""
all_terminating = True
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for instance_id, state in zip(instance_ids, instance_states):
if state is not None:
@@ -160,7 +212,9 @@ def setup_multi_asg_templates(test_session_id):
processor.generate_combined_templates(test_name, template_configs)
# Set environment variables
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
@@ -186,10 +240,19 @@ def _tracking_request_machines(template_name: str, machine_count: int):
yield hfm, template_configs
try:
- cleanup_tracked_requests(_tracked_request_ids, hfm, ec2_client)
+ cleanup_tracked_requests(_tracked_request_ids, hfm, _get_ec2_client())
except Exception as exc:
log.warning("Fixture teardown: cleanup_tracked_requests failed: %s", exc)
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "HF_LOGDIR",
+ ):
+ os.environ.pop(key, None)
processor.cleanup_test_templates(test_name)
log.removeHandler(file_handler)
file_handler.close()
@@ -272,7 +335,7 @@ def provision_asg_capacity( # dead code - commented out
# machines = status_response["requests"][0]["machines"]
#
# instance_ids = [machine.get("machineId") or machine.get("machine_id") for machine in machines]
- # instance_states = get_instances_states(instance_ids, ec2_client)
+ # instance_states = get_instances_states(instance_ids, _get_ec2_client())
#
# for machine, state in zip(machines, instance_states):
# assert machine["status"] in ["running", "pending"]
@@ -468,11 +531,17 @@ def test_multi_asg_termination(setup_multi_asg_templates):
instance_ids = [
machine.get("machineId") or machine.get("machine_id") for machine in machines
]
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for machine, state in zip(machines, instance_states):
assert machine["status"] in ["running", "pending"]
instance_id = machine.get("machineId") or machine.get("machine_id")
+ if machine["status"] == "running":
+ ip = machine.get("privateIpAddress") or machine.get("private_ip_address")
+ assert ip, f"Running machine {instance_id} has empty privateIpAddress"
+ assert _IPV4_RE.match(ip), (
+ f"Running machine {instance_id} has invalid privateIpAddress: {ip!r}"
+ )
assert state is not None
assert state in ["running", "pending"]
log.debug(f"EC2 {instance_id} state: {state}")
@@ -494,7 +563,9 @@ def test_multi_asg_termination(setup_multi_asg_templates):
# Get ASG names from the instances
asg_names = set()
try:
- response = autoscaling_client.describe_auto_scaling_instances(InstanceIds=all_instance_ids)
+ response = _get_autoscaling_client().describe_auto_scaling_instances(
+ InstanceIds=all_instance_ids
+ )
for instance_info in response.get("AutoScalingInstances", []):
asg_name = instance_info.get("AutoScalingGroupName")
@@ -571,7 +642,7 @@ def test_multi_asg_termination(setup_multi_asg_templates):
def check_asg_exists(asg_name: str) -> bool:
"""Check if an ASG still exists."""
try:
- response = autoscaling_client.describe_auto_scaling_groups(
+ response = _get_autoscaling_client().describe_auto_scaling_groups(
AutoScalingGroupNames=[asg_name]
)
return len(response.get("AutoScalingGroups", [])) > 0
@@ -596,7 +667,7 @@ def check_asg_exists(asg_name: str) -> bool:
# Log current ASG status for debugging
try:
- response = autoscaling_client.describe_auto_scaling_groups(
+ response = _get_autoscaling_client().describe_auto_scaling_groups(
AutoScalingGroupNames=[asg_name]
)
if response.get("AutoScalingGroups"):
@@ -631,7 +702,7 @@ def check_asg_exists(asg_name: str) -> bool:
# Log detailed information about remaining ASGs for debugging
for asg_name in final_remaining_asgs:
try:
- response = autoscaling_client.describe_auto_scaling_groups(
+ response = _get_autoscaling_client().describe_auto_scaling_groups(
AutoScalingGroupNames=[asg_name]
)
if response.get("AutoScalingGroups"):
diff --git a/tests/onaws/test_multi_ec2_fleet_termination.py b/tests/providers/aws/live/test_multi_ec2_fleet_termination.py
similarity index 84%
rename from tests/onaws/test_multi_ec2_fleet_termination.py
rename to tests/providers/aws/live/test_multi_ec2_fleet_termination.py
index 5115784fe..ec3952332 100644
--- a/tests/onaws/test_multi_ec2_fleet_termination.py
+++ b/tests/providers/aws/live/test_multi_ec2_fleet_termination.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import re
import time
import uuid
from pathlib import Path
@@ -12,11 +13,11 @@
from jsonschema import ValidationError, validate as validate_json_schema
from hfmock import HostFactoryMock
-from tests.onaws import plugin_io_schemas
-from tests.onaws.cleanup_helpers import cleanup_tracked_requests
-from tests.onaws.parse_output import parse_and_print_output
-from tests.onaws.template_processor import TemplateProcessor
-from tests.onaws.test_onaws import get_instances_states
+from tests.providers.aws.live import plugin_io_schemas
+from tests.providers.aws.live.cleanup_helpers import cleanup_tracked_requests
+from tests.providers.aws.live.parse_output import parse_and_print_output
+from tests.providers.aws.live.template_processor import TemplateProcessor
+from tests.providers.aws.live.test_onaws import get_instances_states
pytestmark = [ # Apply default markers to every test in this module
pytest.mark.manual_aws,
@@ -31,14 +32,50 @@
os.environ["AWS_PROVIDER_LOG_DIR"] = "./logs"
os.environ["LOG_DESTINATION"] = "file"
-_boto_session = boto3.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
+_ec2_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config, same as test_onaws._get_boto_clients."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ # Fall back to AWS_PROFILE env var so the explicit profile_name is always
+ # non-None when a profile is available. This forces botocore to use
+ # profile-based credentials and ignore injected fake env var credentials.
+ profile = profile or os.environ.get("AWS_PROFILE")
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
log = logging.getLogger("multi_ec2_fleet_test")
log.setLevel(logging.DEBUG)
@@ -51,28 +88,42 @@
log.addHandler(console_handler)
MAX_TIME_WAIT_FOR_CAPACITY_PROVISIONING_SEC = 180
+_IPV4_RE = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
def get_ec2_fleet_instances(fleet_id: str) -> List[str]:
"""Get all instance IDs from an EC2 Fleet."""
try:
- response = ec2_client.describe_fleet_instances(FleetId=fleet_id)
+ response = _get_ec2_client().describe_fleet_instances(FleetId=fleet_id)
return [instance["InstanceId"] for instance in response.get("ActiveInstances", [])]
except ClientError as e:
log.error(f"Error getting EC2 Fleet instances for {fleet_id}: {e}")
return []
-def verify_ec2_fleet_instances_detached(instance_ids: List[str]) -> bool:
- """Verify that instances are no longer part of any EC2 Fleet."""
+def verify_ec2_fleet_instances_detached(
+ instance_ids: List[str],
+ fleet_ids: List[str] | None = None,
+) -> bool:
+ """Verify that instances are no longer part of any EC2 Fleet.
+
+ When *fleet_ids* is supplied the check is scoped to those specific fleets
+ (the ones created by this test run). Without it the call falls back to
+ listing every active fleet in the region, which can be slow and may
+ include stale fleets from earlier test runs.
+ """
try:
if not instance_ids:
return True
- # Get all active EC2 fleets
- response = ec2_client.describe_fleets(
- Filters=[{"Name": "fleet-state", "Values": ["active", "modifying"]}]
- )
+ if fleet_ids:
+ # Fast path: only inspect the fleets we created in this test.
+ response = _get_ec2_client().describe_fleets(FleetIds=list(fleet_ids))
+ else:
+ # Fallback: scan every active fleet in the region.
+ response = _get_ec2_client().describe_fleets(
+ Filters=[{"Name": "fleet-state", "Values": ["active", "modifying"]}]
+ )
# Check each fleet for our instances
for fleet in response.get("Fleets", []):
@@ -81,7 +132,7 @@ def verify_ec2_fleet_instances_detached(instance_ids: List[str]) -> bool:
continue
try:
- fleet_instances = ec2_client.describe_fleet_instances(FleetId=fleet_id)
+ fleet_instances = _get_ec2_client().describe_fleet_instances(FleetId=fleet_id)
active_instance_ids = [
inst["InstanceId"] for inst in fleet_instances.get("ActiveInstances", [])
]
@@ -114,7 +165,7 @@ def verify_ec2_fleet_instances_detached(instance_ids: List[str]) -> bool:
def check_all_instances_terminating_or_terminated(instance_ids: List[str]) -> bool:
"""Check if all instances are in terminating or terminated state."""
all_terminating = True
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for instance_id, state in zip(instance_ids, instance_states):
if state is not None:
@@ -177,7 +228,9 @@ def setup_multi_ec2_fleet_templates(test_session_id):
processor.generate_combined_templates(test_name, template_configs)
# Set environment variables
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
@@ -203,10 +256,19 @@ def _tracking_request_machines(template_name: str, machine_count: int):
yield hfm, template_configs
try:
- cleanup_tracked_requests(_tracked_request_ids, hfm, ec2_client)
+ cleanup_tracked_requests(_tracked_request_ids, hfm, _get_ec2_client())
except Exception as exc:
log.warning("Fixture teardown: cleanup_tracked_requests failed: %s", exc)
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "HF_LOGDIR",
+ ):
+ os.environ.pop(key, None)
processor.cleanup_test_templates(test_name)
log.removeHandler(file_handler)
file_handler.close()
@@ -289,7 +351,7 @@ def provision_ec2_fleet_capacity( # dead code - commented out
# machines = status_response["requests"][0]["machines"]
#
# instance_ids = [machine.get("machineId") or machine.get("machine_id") for machine in machines]
- # instance_states = get_instances_states(instance_ids, ec2_client)
+ # instance_states = get_instances_states(instance_ids, _get_ec2_client())
#
# for machine, state in zip(machines, instance_states):
# assert machine["status"] in ["running", "pending"]
@@ -488,11 +550,17 @@ def test_multi_ec2_fleet_termination(setup_multi_ec2_fleet_templates):
instance_ids = [
machine.get("machineId") or machine.get("machine_id") for machine in machines
]
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for machine, state in zip(machines, instance_states):
assert machine["status"] in ["running", "pending"]
instance_id = machine.get("machineId") or machine.get("machine_id")
+ if machine["status"] == "running":
+ ip = machine.get("privateIpAddress") or machine.get("private_ip_address")
+ assert ip, f"Running machine {instance_id} has empty privateIpAddress"
+ assert _IPV4_RE.match(ip), (
+ f"Running machine {instance_id} has invalid privateIpAddress: {ip!r}"
+ )
assert state is not None
assert state in ["running", "pending"]
log.debug(f"EC2 {instance_id} state: {state}")
@@ -514,7 +582,7 @@ def test_multi_ec2_fleet_termination(setup_multi_ec2_fleet_templates):
# Get EC2 Fleet IDs from the instances by checking tags
ec2_fleet_ids = set()
try:
- response = ec2_client.describe_instances(InstanceIds=all_instance_ids)
+ response = _get_ec2_client().describe_instances(InstanceIds=all_instance_ids)
for reservation in response.get("Reservations", []):
for instance in reservation.get("Instances", []):
@@ -533,8 +601,13 @@ def test_multi_ec2_fleet_termination(setup_multi_ec2_fleet_templates):
pytest.fail(f"Failed to verify EC2 Fleet membership: {e}")
log.info(f"Found instances in {len(ec2_fleet_ids)} EC2 Fleets: {list(ec2_fleet_ids)}")
- assert len(ec2_fleet_ids) == 2, (
- f"Expected instances in 2 EC2 Fleets, found {len(ec2_fleet_ids)}"
+ # We provisioned from 2 templates, so we expect at least 2 fleet IDs.
+ # An instant EC2 Fleet with lowestPrice allocation may launch instances from
+ # multiple capacity pools, each tagged with its own aws:ec2:fleet-id. The
+ # exact count therefore varies; what matters is that we got fleets from both
+ # templates (>= 2) and that subsequent termination handles all of them.
+ assert len(ec2_fleet_ids) >= 2, (
+ f"Expected instances in at least 2 EC2 Fleets (one per template), found {len(ec2_fleet_ids)}"
)
# Step 5: Terminate all instances from both EC2 Fleets at once
@@ -564,7 +637,7 @@ def test_multi_ec2_fleet_termination(setup_multi_ec2_fleet_templates):
# Check if instances are detached from EC2 Fleets
if not termination_started:
- detached = verify_ec2_fleet_instances_detached(all_instance_ids)
+ detached = verify_ec2_fleet_instances_detached(all_instance_ids, list(ec2_fleet_ids))
if detached:
log.info("All instances successfully detached from EC2 Fleets")
termination_started = True
@@ -587,7 +660,7 @@ def test_multi_ec2_fleet_termination(setup_multi_ec2_fleet_templates):
assert final_terminating, "Some instances are not in terminating/terminated state"
# Verify instances are detached from EC2 Fleets
- final_detached = verify_ec2_fleet_instances_detached(all_instance_ids)
+ final_detached = verify_ec2_fleet_instances_detached(all_instance_ids, list(ec2_fleet_ids))
if not final_detached:
log.info(
"Note: Some instances still show EC2 Fleet attachment while terminating - this is expected AWS behavior"
@@ -600,7 +673,7 @@ def test_multi_ec2_fleet_termination(setup_multi_ec2_fleet_templates):
def check_ec2_fleet_exists(fleet_id: str) -> bool:
"""Check if an EC2 Fleet still exists and is active."""
try:
- response = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ response = _get_ec2_client().describe_fleets(FleetIds=[fleet_id])
if response.get("Fleets"):
fleet_state = response["Fleets"][0].get("FleetState")
return fleet_state in ["active", "modifying"]
@@ -624,7 +697,7 @@ def check_ec2_fleet_exists(fleet_id: str) -> bool:
if check_ec2_fleet_exists(fleet_id):
# Log current fleet status for debugging
try:
- response = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ response = _get_ec2_client().describe_fleets(FleetIds=[fleet_id])
if response.get("Fleets"):
fleet = response["Fleets"][0]
fleet_state = fleet.get("FleetState", "unknown")
@@ -663,7 +736,7 @@ def check_ec2_fleet_exists(fleet_id: str) -> bool:
# Check if this is a maintain fleet that should have been deleted
try:
- response = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ response = _get_ec2_client().describe_fleets(FleetIds=[fleet_id])
if response.get("Fleets"):
fleet = response["Fleets"][0]
fleet_type = fleet.get("Type", "unknown")
@@ -676,7 +749,7 @@ def check_ec2_fleet_exists(fleet_id: str) -> bool:
# Log detailed information about remaining fleets for debugging
for fleet_id in final_remaining_fleets:
try:
- response = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ response = _get_ec2_client().describe_fleets(FleetIds=[fleet_id])
if response.get("Fleets"):
fleet = response["Fleets"][0]
log.info(f"EC2 Fleet {fleet_id} still exists after termination:")
diff --git a/tests/onaws/test_multi_resource_termination.py b/tests/providers/aws/live/test_multi_resource_termination.py
similarity index 87%
rename from tests/onaws/test_multi_resource_termination.py
rename to tests/providers/aws/live/test_multi_resource_termination.py
index 8129358da..049a19b2b 100644
--- a/tests/onaws/test_multi_resource_termination.py
+++ b/tests/providers/aws/live/test_multi_resource_termination.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import re
import time
import uuid
from pathlib import Path
@@ -12,11 +13,11 @@
from jsonschema import ValidationError, validate as validate_json_schema
from hfmock import HostFactoryMock
-from tests.onaws import plugin_io_schemas
-from tests.onaws.cleanup_helpers import cleanup_tracked_requests
-from tests.onaws.parse_output import parse_and_print_output
-from tests.onaws.template_processor import TemplateProcessor
-from tests.onaws.test_onaws import get_instances_states
+from tests.providers.aws.live import plugin_io_schemas
+from tests.providers.aws.live.cleanup_helpers import cleanup_tracked_requests
+from tests.providers.aws.live.parse_output import parse_and_print_output
+from tests.providers.aws.live.template_processor import TemplateProcessor
+from tests.providers.aws.live.test_onaws import get_instances_states
pytestmark = [ # Apply default markers to every test in this module
pytest.mark.manual_aws,
@@ -31,15 +32,61 @@
os.environ["AWS_PROVIDER_LOG_DIR"] = "./logs"
os.environ["LOG_DESTINATION"] = "file"
-_boto_session = boto3.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
-autoscaling_client = _boto_session.client("autoscaling", region_name=_ec2_region)
+_ec2_client = None
+_autoscaling_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config, same as test_onaws._get_boto_clients."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ # Fall back to AWS_PROFILE env var so the explicit profile_name is always
+ # non-None when a profile is available. This forces botocore to use
+ # profile-based credentials and ignore injected fake env var credentials.
+ profile = profile or os.environ.get("AWS_PROFILE")
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
+
+def _get_autoscaling_client():
+ global _autoscaling_client
+ if _autoscaling_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _autoscaling_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "autoscaling", region_name=_region
+ )
+ return _autoscaling_client
+
log = logging.getLogger("multi_resource_test")
log.setLevel(logging.DEBUG)
@@ -52,12 +99,13 @@
log.addHandler(console_handler)
MAX_TIME_WAIT_FOR_CAPACITY_PROVISIONING_SEC = 180
+_IPV4_RE = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
def check_all_instances_terminating_or_terminated(instance_ids: List[str]) -> bool:
"""Check if all instances are in terminating or terminated state."""
all_terminating = True
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for instance_id, state in zip(instance_ids, instance_states):
if state is not None:
@@ -77,7 +125,7 @@ def categorize_instances_by_resource_type(instance_ids: List[str]) -> Dict[str,
categorized = {"ASG": [], "EC2Fleet": [], "SpotFleet": [], "RunInstances": []}
try:
- response = ec2_client.describe_instances(InstanceIds=instance_ids)
+ response = _get_ec2_client().describe_instances(InstanceIds=instance_ids)
for reservation in response.get("Reservations", []):
for instance in reservation.get("Instances", []):
@@ -108,7 +156,7 @@ def categorize_instances_by_resource_type(instance_ids: List[str]) -> Dict[str,
potential_asg_instances = categorized["RunInstances"].copy()
if potential_asg_instances:
try:
- asg_response = autoscaling_client.describe_auto_scaling_instances(
+ asg_response = _get_autoscaling_client().describe_auto_scaling_instances(
InstanceIds=potential_asg_instances
)
@@ -191,7 +239,9 @@ def setup_multi_resource_templates(test_session_id):
processor.generate_combined_templates(test_name, template_configs)
# Set environment variables
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
@@ -217,10 +267,19 @@ def _tracking_request_machines(template_name: str, machine_count: int):
yield hfm, template_configs
try:
- cleanup_tracked_requests(_tracked_request_ids, hfm, ec2_client)
+ cleanup_tracked_requests(_tracked_request_ids, hfm, _get_ec2_client())
except Exception as exc:
log.warning("Fixture teardown: cleanup_tracked_requests failed: %s", exc)
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "HF_LOGDIR",
+ ):
+ os.environ.pop(key, None)
processor.cleanup_test_templates(test_name)
log.removeHandler(file_handler)
file_handler.close()
@@ -301,7 +360,7 @@ def provision_resource_capacity( # dead code - commented out
# machines = status_response["requests"][0]["machines"]
#
# instance_ids = [machine.get("machineId") or machine.get("machine_id") for machine in machines]
- # instance_states = get_instances_states(instance_ids, ec2_client)
+ # instance_states = get_instances_states(instance_ids, _get_ec2_client())
#
# for machine, state in zip(machines, instance_states):
# assert machine["status"] in ["running", "pending"]
@@ -509,11 +568,17 @@ def test_multi_resource_termination(setup_multi_resource_templates):
instance_ids = [
machine.get("machineId") or machine.get("machine_id") for machine in machines
]
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for machine, state in zip(machines, instance_states):
assert machine["status"] in ["running", "pending"]
instance_id = machine.get("machineId") or machine.get("machine_id")
+ if machine["status"] == "running":
+ ip = machine.get("privateIpAddress") or machine.get("private_ip_address")
+ assert ip, f"Running machine {instance_id} has empty privateIpAddress"
+ assert _IPV4_RE.match(ip), (
+ f"Running machine {instance_id} has invalid privateIpAddress: {ip!r}"
+ )
assert state is not None
assert state in ["running", "pending"]
log.debug(f"EC2 {instance_id} state: {state}")
@@ -590,7 +655,7 @@ def test_multi_resource_termination(setup_multi_resource_templates):
current_categorization = categorize_instances_by_resource_type(all_instance_ids)
for resource_type, instances in current_categorization.items():
if instances:
- instance_states = get_instances_states(instances, ec2_client)
+ instance_states = get_instances_states(instances, _get_ec2_client())
terminating_count = sum(
1 for state in instance_states if state in ["shutting-down", "terminated"]
)
@@ -646,7 +711,7 @@ def test_multi_resource_termination(setup_multi_resource_templates):
for resource_type, instances in final_categorization.items():
if instances:
log.info(f" {resource_type}: {len(instances)} instances")
- instance_states = get_instances_states(instances, ec2_client)
+ instance_states = get_instances_states(instances, _get_ec2_client())
for instance_id, state in zip(instances, instance_states):
log.info(f" {instance_id}: {state if state is not None else 'terminated'}")
diff --git a/tests/onaws/test_multi_spot_fleet_termination.py b/tests/providers/aws/live/test_multi_spot_fleet_termination.py
similarity index 87%
rename from tests/onaws/test_multi_spot_fleet_termination.py
rename to tests/providers/aws/live/test_multi_spot_fleet_termination.py
index a5952e606..9bf840167 100644
--- a/tests/onaws/test_multi_spot_fleet_termination.py
+++ b/tests/providers/aws/live/test_multi_spot_fleet_termination.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import re
import time
import uuid
from pathlib import Path
@@ -12,11 +13,11 @@
from jsonschema import ValidationError, validate as validate_json_schema
from hfmock import HostFactoryMock
-from tests.onaws import plugin_io_schemas
-from tests.onaws.cleanup_helpers import cleanup_tracked_requests
-from tests.onaws.parse_output import parse_and_print_output
-from tests.onaws.template_processor import TemplateProcessor
-from tests.onaws.test_onaws import get_instances_states
+from tests.providers.aws.live import plugin_io_schemas
+from tests.providers.aws.live.cleanup_helpers import cleanup_tracked_requests
+from tests.providers.aws.live.parse_output import parse_and_print_output
+from tests.providers.aws.live.template_processor import TemplateProcessor
+from tests.providers.aws.live.test_onaws import get_instances_states
pytestmark = [ # Apply default markers to every test in this module
pytest.mark.manual_aws,
@@ -31,14 +32,50 @@
os.environ["AWS_PROVIDER_LOG_DIR"] = "./logs"
os.environ["LOG_DESTINATION"] = "file"
-_boto_session = boto3.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
+_ec2_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ # Fall back to AWS_PROFILE env var so the explicit profile_name is always
+ # non-None when a profile is available. This forces botocore to use
+ # profile-based credentials and ignore injected fake env var credentials.
+ profile = profile or os.environ.get("AWS_PROFILE")
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
log = logging.getLogger("multi_spot_fleet_test")
log.setLevel(logging.DEBUG)
@@ -51,12 +88,13 @@
log.addHandler(console_handler)
MAX_TIME_WAIT_FOR_CAPACITY_PROVISIONING_SEC = 300
+_IPV4_RE = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
def get_spot_fleet_instances(fleet_id: str) -> List[str]:
"""Get all instance IDs from a Spot Fleet."""
try:
- response = ec2_client.describe_spot_fleet_instances(SpotFleetRequestId=fleet_id)
+ response = _get_ec2_client().describe_spot_fleet_instances(SpotFleetRequestId=fleet_id)
return [instance["InstanceId"] for instance in response.get("ActiveInstances", [])]
except ClientError as e:
log.error(f"Error getting Spot Fleet instances for {fleet_id}: {e}")
@@ -70,7 +108,7 @@ def verify_spot_fleet_instances_detached(instance_ids: List[str]) -> bool:
return True
# Get all spot fleet requests (no state filter parameter exists in AWS API)
- response = ec2_client.describe_spot_fleet_requests()
+ response = _get_ec2_client().describe_spot_fleet_requests()
# Check each fleet for our instances, but only active/modifying fleets
for fleet in response.get("SpotFleetRequestConfigs", []):
@@ -81,7 +119,7 @@ def verify_spot_fleet_instances_detached(instance_ids: List[str]) -> bool:
continue
try:
- fleet_instances = ec2_client.describe_spot_fleet_instances(
+ fleet_instances = _get_ec2_client().describe_spot_fleet_instances(
SpotFleetRequestId=fleet_id
)
active_instance_ids = [
@@ -116,7 +154,7 @@ def verify_spot_fleet_instances_detached(instance_ids: List[str]) -> bool:
def check_all_instances_terminating_or_terminated(instance_ids: List[str]) -> bool:
"""Check if all instances are in terminating or terminated state."""
all_terminating = True
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for instance_id, state in zip(instance_ids, instance_states):
if state is not None:
@@ -179,7 +217,9 @@ def setup_multi_spot_fleet_templates(test_session_id):
processor.generate_combined_templates(test_name, template_configs)
# Set environment variables
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
@@ -205,10 +245,19 @@ def _tracking_request_machines(template_name: str, machine_count: int):
yield hfm, template_configs
try:
- cleanup_tracked_requests(_tracked_request_ids, hfm, ec2_client)
+ cleanup_tracked_requests(_tracked_request_ids, hfm, _get_ec2_client())
except Exception as exc:
log.warning("Fixture teardown: cleanup_tracked_requests failed: %s", exc)
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "HF_LOGDIR",
+ ):
+ os.environ.pop(key, None)
processor.cleanup_test_templates(test_name)
log.removeHandler(file_handler)
file_handler.close()
@@ -291,7 +340,7 @@ def provision_spot_fleet_capacity( # dead code - commented out
# machines = status_response["requests"][0]["machines"]
#
# instance_ids = [machine.get("machineId") or machine.get("machine_id") for machine in machines]
- # instance_states = get_instances_states(instance_ids, ec2_client)
+ # instance_states = get_instances_states(instance_ids, _get_ec2_client())
#
# for machine, state in zip(machines, instance_states):
# assert machine["status"] in ["running", "pending"]
@@ -488,11 +537,17 @@ def test_multi_spot_fleet_termination(setup_multi_spot_fleet_templates):
instance_ids = [
machine.get("machineId") or machine.get("machine_id") for machine in machines
]
- instance_states = get_instances_states(instance_ids, ec2_client)
+ instance_states = get_instances_states(instance_ids, _get_ec2_client())
for machine, state in zip(machines, instance_states):
assert machine["status"] in ["running", "pending"]
instance_id = machine.get("machineId") or machine.get("machine_id")
+ if machine["status"] == "running":
+ ip = machine.get("privateIpAddress") or machine.get("private_ip_address")
+ assert ip, f"Running machine {instance_id} has empty privateIpAddress"
+ assert _IPV4_RE.match(ip), (
+ f"Running machine {instance_id} has invalid privateIpAddress: {ip!r}"
+ )
assert state is not None
assert state in ["running", "pending"]
log.debug(f"EC2 {instance_id} state: {state}")
@@ -514,7 +569,7 @@ def test_multi_spot_fleet_termination(setup_multi_spot_fleet_templates):
# Get Spot Fleet IDs from the instances by checking tags
spot_fleet_ids = set()
try:
- response = ec2_client.describe_instances(InstanceIds=all_instance_ids)
+ response = _get_ec2_client().describe_instances(InstanceIds=all_instance_ids)
for reservation in response.get("Reservations", []):
for instance in reservation.get("Instances", []):
@@ -598,7 +653,9 @@ def test_multi_spot_fleet_termination(setup_multi_spot_fleet_templates):
log.info("Step 8: Documenting Spot Fleet request state (request-type fleets stay active)")
for fleet_id in spot_fleet_ids:
try:
- response = ec2_client.describe_spot_fleet_requests(SpotFleetRequestIds=[fleet_id])
+ response = _get_ec2_client().describe_spot_fleet_requests(
+ SpotFleetRequestIds=[fleet_id]
+ )
if response.get("SpotFleetRequestConfigs"):
fleet = response["SpotFleetRequestConfigs"][0]
state = fleet.get("SpotFleetRequestState", "unknown")
diff --git a/tests/onaws/test_onaws.py b/tests/providers/aws/live/test_onaws.py
similarity index 90%
rename from tests/onaws/test_onaws.py
rename to tests/providers/aws/live/test_onaws.py
index 10d7135da..b760a8e86 100644
--- a/tests/onaws/test_onaws.py
+++ b/tests/providers/aws/live/test_onaws.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import re
import time
from typing import Optional
@@ -10,7 +11,7 @@
from jsonschema import ValidationError, validate as validate_json_schema
from hfmock import HostFactoryMock
-from tests.onaws import plugin_io_schemas, scenarios
+from tests.providers.aws.live import plugin_io_schemas, scenarios
# EC2Fleet + maintain combinations are now stable and re-enabled here.
# They were previously commented out in scenarios.DEFAULT_ATTRIBUTE_COMBINATIONS.
@@ -21,12 +22,12 @@
"scheduler": ["default", "hostfactory"],
}
scenarios.DEFAULT_ATTRIBUTE_COMBINATIONS.append(_EC2FLEET_MAINTAIN_COMBINATIONS)
-from tests.onaws.cleanup_helpers import (
+from tests.providers.aws.live.cleanup_helpers import (
cleanup_launch_templates_for_request,
wait_for_instances_terminated,
)
-from tests.onaws.parse_output import parse_and_print_output
-from tests.onaws.template_processor import TemplateProcessor
+from tests.providers.aws.live.parse_output import parse_and_print_output
+from tests.providers.aws.live.template_processor import TemplateProcessor
pytestmark = [ # Apply default markers to every test in this module
pytest.mark.manual_aws,
@@ -61,6 +62,12 @@ def _get_boto_clients():
except Exception:
pass # Fall back to defaults
+ # Fall back to AWS_PROFILE so the explicit profile_name is non-None when a
+ # profile is available. pytest-env injects AWS_ACCESS_KEY_ID=testing for
+ # unit tests; botocore resolves env-var credentials before profile
+ # credentials, so any session without an explicit profile_name uses the
+ # fake creds and gets AuthFailure. An explicit profile_name bypasses them.
+ profile = profile or os.environ.get("AWS_PROFILE")
region = (
region
or os.environ.get("AWS_REGION")
@@ -388,6 +395,106 @@ def _get_resource_id_from_instance(instance_id: str, provider_api: str) -> Optio
return None
+def _get_asg_instance_weight(instance_id: str, asg_name: str) -> int:
+ """Return the WeightedCapacity of an instance within an ASG.
+
+ Looks up the instance type via EC2 describe_instances (works even after the
+ instance is detached from the ASG), then finds the matching WeightedCapacity
+ in the ASG's MixedInstancesPolicy overrides.
+ Returns 1 if the ASG has no per-type weights (uniform fleet).
+ """
+ ec2_client, asg_client = _get_boto_clients()
+ try:
+ # Use EC2 describe_instances so we still get the instance type even
+ # after it has been detached from the ASG.
+ ec2_resp = ec2_client.describe_instances(InstanceIds=[instance_id])
+ reservations = ec2_resp.get("Reservations") or []
+ if not reservations:
+ return 1
+ instances = reservations[0].get("Instances") or []
+ if not instances:
+ return 1
+ instance_type = instances[0].get("InstanceType") or ""
+
+ asg_resp = asg_client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])
+ asgs = asg_resp.get("AutoScalingGroups") or [{}]
+ asg = asgs[0]
+
+ policy = asg.get("MixedInstancesPolicy") or {}
+ lt = policy.get("LaunchTemplate") or {}
+ overrides = lt.get("Overrides") or []
+
+ for override in overrides:
+ if override.get("InstanceType") == instance_type:
+ wc = override.get("WeightedCapacity")
+ if wc is not None:
+ return int(wc)
+ return 1
+ except (ClientError, KeyError) as exc:
+ log.warning("Failed to look up ASG instance weight for %s: %s", instance_id, exc)
+ return 1
+
+
+def _get_fleet_instance_weight(instance_id: str, fleet_id: str, provider_api: str) -> int:
+ """Return the WeightedCapacity of an instance in an EC2Fleet or SpotFleet.
+
+ Reads the instance type via EC2 ``describe_instances`` (works even after
+ the instance is detached / terminated by the fleet), then looks up the
+ matching ``WeightedCapacity`` in the fleet's launch-template overrides.
+ Falls back to 1 if the fleet has no per-type weights.
+
+ ``describe_fleet_instances`` / ``describe_spot_fleet_instances`` only list
+ *currently active* instances, so they return nothing for instances that
+ have already been returned — the older implementation always saw an empty
+ list at this point and silently fell back to 1.
+ """
+ ec2_client, _ = _get_boto_clients()
+ try:
+ ec2_resp = ec2_client.describe_instances(InstanceIds=[instance_id])
+ reservations = ec2_resp.get("Reservations") or []
+ if not reservations:
+ return 1
+ instances = reservations[0].get("Instances") or []
+ if not instances:
+ return 1
+ instance_type = instances[0].get("InstanceType") or ""
+
+ is_spot = "spotfleet" in provider_api.lower() or fleet_id.startswith("sfr-")
+ if is_spot:
+ resp = ec2_client.describe_spot_fleet_requests(SpotFleetRequestIds=[fleet_id])
+ configs = resp.get("SpotFleetRequestConfigs") or [{}]
+ sfr = configs[0].get("SpotFleetRequestConfig") or {}
+ for spec in sfr.get("LaunchSpecifications") or []:
+ if spec.get("InstanceType") == instance_type:
+ wc = spec.get("WeightedCapacity")
+ if wc is not None:
+ return int(float(wc))
+ for tpl in sfr.get("LaunchTemplateConfigs") or []:
+ for override in tpl.get("Overrides") or []:
+ if override.get("InstanceType") == instance_type:
+ wc = override.get("WeightedCapacity")
+ if wc is not None:
+ return int(float(wc))
+ else:
+ resp = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ fleets = resp.get("Fleets") or [{}]
+ for ltc in fleets[0].get("LaunchTemplateConfigs") or []:
+ for override in ltc.get("Overrides") or []:
+ if override.get("InstanceType") == instance_type:
+ wc = override.get("WeightedCapacity")
+ if wc is not None:
+ return int(float(wc))
+ return 1
+ except (ClientError, KeyError) as exc:
+ log.warning(
+ "Failed to look up fleet instance weight for %s in %s: %s",
+ instance_id,
+ fleet_id,
+ exc,
+ )
+ return 1
+
+
def _get_capacity(provider_api: str, resource_id: str) -> int:
"""Return target/desired capacity for fleet or ASG."""
ec2_client, asg_client = _get_boto_clients()
@@ -948,7 +1055,7 @@ def setup_host_factory_mock_with_scenario(request, monkeypatch, test_session_id)
if "[" in test_name and "]" in test_name:
scenario_name = test_name.split("[")[1].split("]")[0]
- from tests.onaws import scenarios
+ from tests.providers.aws.live import scenarios
test_case = scenarios.get_test_case_by_name(scenario_name) if scenario_name else {}
@@ -1039,11 +1146,21 @@ def _tracking_request_machines(template_name: str, machine_count: int):
log.info("--keep-logs set: preserving test directory %s", test_config_dir)
+_IPV4_RE = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
+
+
def _check_request_machines_response_status(status_response):
assert status_response["requests"][0]["status"] == "complete"
for machine in status_response["requests"][0]["machines"]:
# it is possible that ec2 host is still initialising
assert machine["status"] in ["running", "pending"]
+ if machine["status"] == "running":
+ machine_id = machine.get("machineId") or machine.get("machine_id")
+ ip = machine.get("privateIpAddress") or machine.get("private_ip_address")
+ assert ip, f"Running machine {machine_id} has empty privateIpAddress"
+ assert _IPV4_RE.match(ip), (
+ f"Running machine {machine_id} has invalid privateIpAddress: {ip!r}"
+ )
def _check_all_ec2_hosts_are_being_provisioned(status_response):
@@ -1204,9 +1321,9 @@ def _verify_all_resources_cleaned(
instances_cleaned = False
if instances_cleaned:
- log.info("✅ All instances are terminated or terminating")
+ log.info("All instances are terminated or terminating")
else:
- log.error("❌ Some instances are still running")
+ log.error("Some instances are still running")
# Check backing resource
resource_cleaned = True
@@ -1226,10 +1343,10 @@ def _verify_all_resources_cleaned(
log.error("ASG %s still has %d instances", resource_id, len(instances))
resource_cleaned = False
else:
- log.info("✅ ASG %s has no instances", resource_id)
+ log.info("ASG %s has no instances", resource_id)
except ClientError as e:
if e.response["Error"]["Code"] == "ValidationError":
- log.info("✅ ASG %s no longer exists", resource_id)
+ log.info("ASG %s no longer exists", resource_id)
else:
log.error("Error checking ASG %s: %s", resource_id, e)
resource_cleaned = False
@@ -1241,18 +1358,16 @@ def _verify_all_resources_cleaned(
log.error("Fleet %s still has capacity: %d", resource_id, capacity)
resource_cleaned = False
else:
- log.info("✅ Fleet %s has zero capacity", resource_id)
+ log.info("Fleet %s has zero capacity", resource_id)
except Exception as e:
- log.info(
- "✅ Fleet %s appears to be deleted or inaccessible: %s", resource_id, e
- )
+ log.info("Fleet %s appears to be deleted or inaccessible: %s", resource_id, e)
except Exception as e:
log.warning("Could not verify backing resource cleanup: %s", e)
if resource_cleaned:
- log.info("✅ Backing resource is properly cleaned")
+ log.info("Backing resource is properly cleaned")
else:
- log.error("❌ Backing resource still has active resources")
+ log.error("Backing resource still has active resources")
return instances_cleaned and resource_cleaned
@@ -1482,6 +1597,37 @@ def provide_release_control_loop(hfm, template_json, capacity_to_request, test_c
_check_all_ec2_hosts_are_being_provisioned(status_response)
+ # Capacity-unit fulfilment check from provider response fields.
+ # target_units / fulfilled_units reflect capacity units for weighted fleets
+ # and instance count for unweighted templates (1 unit == 1 instance).
+ _req0 = status_response.get("requests", [{}])[0] if isinstance(status_response, dict) else {}
+ _machine_ids_for_check = [
+ m.get("machineId") or m.get("machine_id")
+ for m in _req0.get("machines", [])
+ if m.get("machineId") or m.get("machine_id")
+ ]
+ _target_units = (
+ _req0["target_units"] if _req0.get("target_units") is not None else capacity_to_request
+ )
+ _fulfilled_units = (
+ _req0["fulfilled_units"]
+ if _req0.get("fulfilled_units") is not None
+ else len(_machine_ids_for_check)
+ )
+ assert _fulfilled_units >= _target_units, (
+ f"Fleet not fully fulfilled: fulfilled={_fulfilled_units}, target={_target_units}"
+ )
+ # Template-aware instance count sanity check.
+ if test_case and scenarios.template_uses_weighted_capacity(test_case):
+ assert len(_machine_ids_for_check) >= 1, (
+ f"Expected at least 1 machine (weighted template), got: {_machine_ids_for_check}"
+ )
+ else:
+ assert len(_machine_ids_for_check) == capacity_to_request, (
+ f"Expected {capacity_to_request} machines (unweighted template), "
+ f"got {len(_machine_ids_for_check)}: {_machine_ids_for_check}"
+ )
+
# Validate instance attributes against template
log.info("Starting instance attribute validation against template")
attribute_validation_passed = validate_random_instance_attributes(
@@ -1611,14 +1757,14 @@ def provide_release_control_loop(hfm, template_json, capacity_to_request, test_c
cleanup_verified = _verify_all_resources_cleaned(ec2_instance_ids, resource_id, provider_api)
if not cleanup_verified:
- log.error("⚠️ Cleanup verification failed - some resources may still exist")
+ log.error("Cleanup verification failed - some resources may still exist")
# Log remaining resources for debugging
for instance_id in ec2_instance_ids:
state_info = get_instance_state(instance_id)
if state_info["exists"]:
log.error("Instance %s still exists in state: %s", instance_id, state_info["state"])
else:
- log.info("✅ All resources successfully cleaned up")
+ log.info("All resources successfully cleaned up")
@pytest.mark.aws
@@ -1813,7 +1959,10 @@ def test_partial_return_reduces_capacity(setup_host_factory_mock_with_scenario,
log.info("1.6: Extracting provisioned instance information")
machines = status_response["requests"][0]["machines"]
machine_ids = [m.get("machineId") or m.get("machine_id") for m in machines]
- assert len(machine_ids) >= 2, "Partial return test requires capacity > 1"
+ if len(machine_ids) < 2:
+ pytest.skip(
+ f"Only {len(machine_ids)} physical instance(s) provisioned (weighted capacity scenario) — need 2+ for partial return test"
+ )
log.info("Provisioned %d instances: %s", len(machine_ids), machine_ids)
# === STEP 2: PARTIAL RETURN AND CAPACITY VERIFICATION ===
@@ -1863,7 +2012,29 @@ def test_partial_return_reduces_capacity(setup_host_factory_mock_with_scenario,
# 2.6: Verify capacity reduction
log.info("2.6: Verifying capacity reduction")
- expected_capacity = max(capacity_before - 1, 0)
+ # For ASGs with weighted instance types, returning one instance reduces
+ # DesiredCapacity by the instance's WeightedCapacity (e.g. 2 for t3.medium
+ # with weight=2), not just by 1. Look up the actual weight so we assert the
+ # correct expected value regardless of which instance type AWS placed.
+ if provider_api == "ASG" or "asg" in provider_api.lower():
+ capacity_decrement = _get_asg_instance_weight(first_instance, resource_id)
+ log.info(
+ "ASG weighted capacity: instance %s has weight %d in ASG %s",
+ first_instance,
+ capacity_decrement,
+ resource_id,
+ )
+ elif provider_api in ("EC2Fleet", "SpotFleet") or resource_id.startswith(("fleet-", "sfr-")):
+ capacity_decrement = _get_fleet_instance_weight(first_instance, resource_id, provider_api)
+ log.info(
+ "Fleet weighted capacity: instance %s has weight %d in fleet %s",
+ first_instance,
+ capacity_decrement,
+ resource_id,
+ )
+ else:
+ capacity_decrement = 1
+ expected_capacity = max(capacity_before - capacity_decrement, 0)
# ASG operations can take longer than fleet operations
timeout = 120 if provider_api == "ASG" or "asg" in provider_api.lower() else 60
log.info("Waiting for capacity change with timeout=%ds", timeout)
@@ -1944,7 +2115,7 @@ def test_partial_return_reduces_capacity(setup_host_factory_mock_with_scenario,
cleanup_verified = _verify_all_resources_cleaned(remaining_ids, resource_id, provider_api)
if not cleanup_verified:
- log.error("⚠️ Cleanup verification failed - some resources may still exist")
+ log.error("Cleanup verification failed - some resources may still exist")
# Log remaining resources for debugging
for instance_id in remaining_ids:
state_info = get_instance_state(instance_id)
@@ -1953,7 +2124,7 @@ def test_partial_return_reduces_capacity(setup_host_factory_mock_with_scenario,
"Instance %s still exists in state: %s", instance_id, state_info["state"]
)
else:
- log.info("✅ All resources successfully cleaned up")
+ log.info("All resources successfully cleaned up")
else:
log.info("3.1: No remaining instances to clean up")
diff --git a/tests/onaws/test_rest_api_onaws.py b/tests/providers/aws/live/test_rest_api_onaws.py
similarity index 85%
rename from tests/onaws/test_rest_api_onaws.py
rename to tests/providers/aws/live/test_rest_api_onaws.py
index 4027ce38c..5aa04b08d 100644
--- a/tests/onaws/test_rest_api_onaws.py
+++ b/tests/providers/aws/live/test_rest_api_onaws.py
@@ -16,13 +16,13 @@
import requests
from botocore.exceptions import ClientError
-from tests.onaws import scenarios_rest_api
-from tests.onaws.scenarios_rest_api import CUSTOM_TEST_CASES
-from tests.onaws.template_processor import TemplateProcessor
+from tests.providers.aws.live import scenarios_rest_api
+from tests.providers.aws.live.scenarios_rest_api import CUSTOM_TEST_CASES
+from tests.providers.aws.live.template_processor import TemplateProcessor
# Import AWS validation functions from test_onaws (guarded to allow skip on import failures)
try:
- from tests.onaws.test_onaws import (
+ from tests.providers.aws.live.test_onaws import (
_check_all_ec2_hosts_are_being_terminated,
_cleanup_asg_resources,
_get_capacity,
@@ -57,19 +57,59 @@
# Force region to eu-west-1 for tests
os.environ.setdefault("AWS_REGION", "eu-west-1")
-# AWS client setup
-_boto_session = boto3.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
-asg_client = _boto_session.client("autoscaling", region_name=_ec2_region)
+# AWS client setup — lazy-init so the client is constructed after
+# pytest_sessionstart has exported AWS_PROFILE from the ORB config.
+_ec2_client = None
+_asg_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
+
+def _get_asg_client():
+ global _asg_client
+ if _asg_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _asg_client = boto3.Session(profile_name=_profile, region_name=_region).client(
+ "autoscaling", region_name=_region
+ )
+ return _asg_client
-# Log the configured region for debugging
-print(f"AWS clients initialized with region: {_ec2_region}")
# Logger setup
log = logging.getLogger("rest_api_test")
@@ -109,36 +149,60 @@ def __init__(self, message: str, status_response: dict | None = None):
self.status_response = status_response
+def _pick_free_port() -> int:
+ """Bind to port 0 and immediately release it to claim an OS-assigned ephemeral port.
+
+ Avoids hard-coded ports colliding across pytest-xdist workers running in parallel.
+ There is a small TOCTOU race between releasing and the server binding — acceptable
+ for test isolation; the kernel does not reuse a port immediately under normal load.
+ """
+ import socket
+
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(("127.0.0.1", 0))
+ return s.getsockname()[1]
+
+
class ORBServerManager:
"""Manage ORB server lifecycle for testing."""
def __init__(
self,
host: str = scenarios_rest_api.REST_API_SERVER["host"],
- port: int = scenarios_rest_api.REST_API_SERVER["port"],
+ port: Optional[int] = None,
log_path: Optional[str] = None,
):
self.host = host
- self.port = port
+ # Auto-pick a free port per instance unless explicitly overridden.
+ # Configured `port` in scenarios is only honoured when explicitly passed
+ # (e.g. a single-worker run that wants to hit a known port).
+ self.port = port if port is not None else _pick_free_port()
self.process = None
- self.base_url = f"http://{host}:{port}"
+ # Health probe must target localhost (not 0.0.0.0 or wildcard host).
+ self.base_url = f"http://127.0.0.1:{self.port}"
self.log_path = log_path
self._log_file_handle = None
+ self._captured_log_path: Optional[str] = None
def start(self, timeout: int | None = None):
- """Start ORB server: orb system serve --host 0.0.0.0 --port 8000"""
+ """Start ORB server: orb system serve --host --port """
cmd = ["orb", "system", "serve", "--host", self.host, "--port", str(self.port)]
- log.info(f"Starting ORB server: {' '.join(cmd)}")
+ log.info("Starting ORB server: %s", " ".join(cmd))
- stdout_target = subprocess.PIPE
- stderr_target = subprocess.PIPE
+ # Always capture server output to a file so a failed start has actionable detail.
+ if not self.log_path:
+ # Fall back to a temp file the test can surface in the error message.
+ import tempfile
- # If a log path is provided, write combined stdout/stderr to that file
- if self.log_path:
+ fd, self.log_path = tempfile.mkstemp(prefix=f"orb-server-{self.port}-", suffix=".log")
+ os.close(fd)
+ else:
os.makedirs(os.path.dirname(self.log_path), exist_ok=True)
- self._log_file_handle = open(self.log_path, "w", encoding="utf-8")
- stdout_target = self._log_file_handle
- stderr_target = subprocess.STDOUT
+
+ self._captured_log_path = self.log_path
+ self._log_file_handle = open(self.log_path, "w", encoding="utf-8")
+ stdout_target = self._log_file_handle
+ stderr_target = subprocess.STDOUT
if timeout is None:
timeout = REST_TIMEOUTS["server_start"]
@@ -150,31 +214,50 @@ def start(self, timeout: int | None = None):
text=True,
)
- # Wait for server to be ready
+ # Wait for server to be ready, AND fail fast if the process dies.
start_time = time.time()
while time.time() - start_time < timeout:
+ # If the process has exited, no point continuing to poll.
+ if self.process.poll() is not None:
+ break
try:
response = requests.get(
f"{self.base_url}/health",
timeout=scenarios_rest_api.REST_API_SERVER["start_probe_timeout"],
)
if response.status_code == 200:
- log.info(f"ORB server started successfully on {self.base_url}")
+ log.info("ORB server started successfully on %s", self.base_url)
return
except requests.exceptions.RequestException:
time.sleep(scenarios_rest_api.REST_API_SERVER["start_probe_interval"])
- # Server failed to start - capture output
- try:
- _stdout, stderr = self.process.communicate(
- timeout=scenarios_rest_api.REST_API_SERVER["start_capture_timeout"]
- )
- error_msg = f"ORB server failed to start within {timeout}s. stderr: {stderr}"
- except subprocess.TimeoutExpired:
- error_msg = f"ORB server failed to start within {timeout}s (process still running)"
-
+ # Server failed to start — read captured output from the log file.
+ # The subprocess.PIPE/communicate dance doesn't work here because we
+ # already redirected output to the log file.
+ captured = self._read_captured_output()
+ exit_code = self.process.poll() if self.process else None
+ error_msg = (
+ f"ORB server failed to start within {timeout}s on {self.base_url} "
+ f"(exit_code={exit_code}, log={self._captured_log_path}).\n"
+ f"--- captured output (last 2KB) ---\n{captured}"
+ )
raise RuntimeError(error_msg)
+ def _read_captured_output(self) -> str:
+ """Read the last 2KB of the server log for error context."""
+ try:
+ if self._log_file_handle:
+ self._log_file_handle.flush()
+ if not self._captured_log_path or not os.path.exists(self._captured_log_path):
+ return "(no log file)"
+ size = os.path.getsize(self._captured_log_path)
+ with open(self._captured_log_path, encoding="utf-8", errors="replace") as f:
+ if size > 2048:
+ f.seek(size - 2048)
+ return f.read()
+ except Exception as exc: # noqa: BLE001
+ return f"(failed to read log: {exc})"
+
def stop(self):
"""Terminate ORB server process."""
if self.process:
@@ -273,10 +356,10 @@ def return_machines(self, machine_ids: List[str]) -> dict:
return self._handle_response(response)
def get_request_details(self, request_id: str) -> dict:
- """GET /api/v1/requests/{request_id}"""
- log.debug(f"GET /api/v1/requests/{request_id}")
+ """GET /api/v1/requests/{request_id}/status — bare /requests/{id} was removed."""
+ log.debug(f"GET /api/v1/requests/{request_id}/status")
response = self.session.get(
- self._url(f"/requests/{request_id}"),
+ self._url(f"/requests/{request_id}/status"),
timeout=self.timeout,
)
return self._handle_response(response)
@@ -326,7 +409,9 @@ def setup_rest_api_environment(request, test_session_id):
)
# Configure environment (must be set before server start)
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["DEFAULT_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
@@ -343,6 +428,22 @@ def setup_rest_api_environment(request, test_session_id):
yield test_case
+ for key in (
+ "ORB_CONFIG_DIR",
+ "HF_PROVIDER_CONFDIR",
+ "HF_PROVIDER_LOGDIR",
+ "HF_PROVIDER_WORKDIR",
+ "DEFAULT_PROVIDER_WORKDIR",
+ "AWS_PROVIDER_LOG_DIR",
+ "METRICS_DIR",
+ ):
+ os.environ.pop(key, None)
+ try:
+ from orb.infrastructure.di import reset_container
+
+ reset_container()
+ except Exception:
+ pass
try:
processor.cleanup_test_templates(test_name)
except Exception as exc:
@@ -387,7 +488,7 @@ def _lookup_aws_capacity_progress(provider_api: str, resource_id: str) -> tuple[
try:
if "spotfleet" in provider_lower:
- resp = ec2_client.describe_spot_fleet_requests(SpotFleetRequestIds=[resource_id])
+ resp = _get_ec2_client().describe_spot_fleet_requests(SpotFleetRequestIds=[resource_id])
configs = resp.get("SpotFleetRequestConfigs") or []
config = configs[0].get("SpotFleetRequestConfig", {}) if configs else {}
target = int(config.get("TargetCapacity", 0) or 0)
@@ -395,7 +496,7 @@ def _lookup_aws_capacity_progress(provider_api: str, resource_id: str) -> tuple[
return fulfilled, target
if "ec2fleet" in provider_lower:
- resp = ec2_client.describe_fleets(FleetIds=[resource_id])
+ resp = _get_ec2_client().describe_fleets(FleetIds=[resource_id])
fleets = resp.get("Fleets") or []
fleet = fleets[0] if fleets else {}
target_spec = fleet.get("TargetCapacitySpecification", {}) or {}
@@ -404,7 +505,9 @@ def _lookup_aws_capacity_progress(provider_api: str, resource_id: str) -> tuple[
return fulfilled, target
if provider_lower == "asg" or "autoscaling" in provider_lower:
- resp = asg_client.describe_auto_scaling_groups(AutoScalingGroupNames=[resource_id])
+ resp = _get_asg_client().describe_auto_scaling_groups(
+ AutoScalingGroupNames=[resource_id]
+ )
asg = (resp.get("AutoScalingGroups") or [{}])[0]
target = int(asg.get("DesiredCapacity", 0) or 0)
fulfilled = len(asg.get("Instances") or [])
@@ -440,9 +543,9 @@ def _log_aws_capacity_progress(
machines = first_request.get("machines") or []
machine_ids = [
- machine.get("machine_id")
+ machine.get("machineId") or machine.get("machine_id")
for machine in machines
- if isinstance(machine, dict) and machine.get("machine_id")
+ if isinstance(machine, dict) and (machine.get("machineId") or machine.get("machine_id"))
]
if not machine_ids:
@@ -550,13 +653,10 @@ def log_resource_history(resource_id: str, provider_api: str) -> None:
next_token = None
# Add 60 second buffer to account for clock skew between client and AWS
buffered_start_time = start_time - timedelta(seconds=3600)
- # Set end time 5 hours in the future to ensure we get all events
- end_time = current_time + timedelta(hours=5)
while api_call_count < config["max_api_calls"]:
params = {
"FleetId": resource_id,
"StartTime": buffered_start_time,
- "EndTime": end_time, # Use calculated end time for bounded queries
"MaxResults": config["max_records_per_call"],
}
if next_token:
@@ -567,7 +667,7 @@ def log_resource_history(resource_id: str, provider_api: str) -> None:
f"EC2Fleet history API call {api_call_count}/{config['max_api_calls']}, requesting up to {params['MaxResults']} records"
)
- response = ec2_client.describe_fleet_history(**params)
+ response = _get_ec2_client().describe_fleet_history(**params)
all_records = response.get("HistoryRecords", [])
# Filter records to exclude already-seen records
@@ -612,7 +712,7 @@ def log_resource_history(resource_id: str, provider_api: str) -> None:
api_call_count += 1
log.debug(f"SpotFleet history API call {api_call_count}/{config['max_api_calls']}")
- response = ec2_client.describe_spot_fleet_request_history(**params)
+ response = _get_ec2_client().describe_spot_fleet_request_history(**params)
all_records = response.get("HistoryRecords", [])
# Filter records to exclude already-seen records
@@ -641,7 +741,7 @@ def log_resource_history(resource_id: str, provider_api: str) -> None:
elif provider_api == "ASG":
log.debug(
- f"Fetching ASG scaling activities for {resource_id} (region={asg_client.meta.region_name})"
+ f"Fetching ASG scaling activities for {resource_id} (region={_get_asg_client().meta.region_name})"
)
# ASG has a max limit of 100 records per call, so we need to paginate
next_token = None
@@ -657,7 +757,7 @@ def log_resource_history(resource_id: str, provider_api: str) -> None:
api_call_count += 1
# log.debug(f"ASG history API call {api_call_count}, requesting up to {params['MaxRecords']} records")
- response = asg_client.describe_scaling_activities(**params)
+ response = _get_asg_client().describe_scaling_activities(**params)
all_activities = response.get("Activities", [])
# log.debug(f"Retrieved {len(all_activities)} ASG activities from API")
@@ -906,12 +1006,26 @@ def _wait_for_return_completion_rest(
status_response = client.get_request_details(return_request_id)
log.debug(f"Return request status: {json.dumps(status_response, indent=2)}")
- # Check if request is complete
- if status_response.get("status") == "complete":
+ # /requests/{id}/status wraps results in a "requests" list
+ req_list = status_response.get("requests", [])
+ req_status = req_list[0].get("status") if req_list else status_response.get("status")
+ if req_status == "complete":
log.info(f"Return request {return_request_id} completed")
return status_response
except requests.HTTPError as e:
- log.debug(f"Error checking return status: {e}")
+ status_code = e.response.status_code if e.response is not None else None
+ log.debug(f"Error checking return status (HTTP {status_code}): {e}")
+ # Non-retryable client errors (not 429 Too Many Requests or 503 Service Unavailable)
+ if (
+ status_code is not None
+ and status_code not in (429, 503)
+ and 400 <= status_code < 500
+ ):
+ log.warning(
+ f"Non-retryable HTTP {status_code} for return request {return_request_id}; "
+ "aborting poll"
+ )
+ return {}
if time.time() - start_time > timeout:
log.warning(f"Return request {return_request_id} did not complete within {timeout}s")
@@ -953,11 +1067,15 @@ def test_rest_api_partial_return_reduces_capacity(
# Step 1: Request capacity
templates_response = rest_api_client.get_templates()
template_id = test_case.get("template_id") or test_case["test_name"]
+ scheduler = test_case.get("overrides", {}).get("scheduler", "default")
+ template_id_key = "templateId" if scheduler == "hostfactory" else "template_id"
+ provider_api_key = "providerApi" if scheduler == "hostfactory" else "provider_api"
+
template_json = next(
(
template
for template in templates_response["templates"]
- if template.get("template_id") == template_id
+ if template.get(template_id_key) == template_id
),
None,
)
@@ -965,14 +1083,14 @@ def test_rest_api_partial_return_reduces_capacity(
pytest.fail(f"Template {template_id} not found for partial return test")
provider_api = (
- template_json.get("provider_api")
+ template_json.get(provider_api_key)
or test_case.get("overrides", {}).get("providerApi")
or "EC2Fleet"
)
log.info("Requesting %d instances", test_case["capacity_to_request"])
request_response = rest_api_client.request_machines(
- template_id=template_json["template_id"],
+ template_id=template_json[template_id_key],
machine_count=test_case["capacity_to_request"],
)
log.debug("Request response: %s", json.dumps(request_response, indent=2))
@@ -1132,12 +1250,22 @@ def test_00_rest_api_server_health(setup_rest_api_environment):
assert down_confirmed, "API should be unreachable after server.stop()"
+_IPV4_RE = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
+
+
def _check_request_machines_response_status(status_response):
"""Validate request status response."""
assert status_response["requests"][0]["status"] == "complete"
for machine in status_response["requests"][0]["machines"]:
# EC2 host may still be initializing
assert machine["status"] in ["running", "pending"]
+ if machine["status"] == "running":
+ machine_id = machine.get("machineId") or machine.get("machine_id")
+ ip = machine.get("privateIpAddress") or machine.get("private_ip_address")
+ assert ip, f"Running machine {machine_id} has empty privateIpAddress"
+ assert _IPV4_RE.match(ip), (
+ f"Running machine {machine_id} has invalid privateIpAddress: {ip!r}"
+ )
def _describe_instances_bulk(instance_ids: list[str], chunk_size: int = 100) -> dict[str, dict]:
@@ -1147,13 +1275,18 @@ def _describe_instances_bulk(instance_ids: list[str], chunk_size: int = 100) ->
Returns a mapping of instance_id -> {"exists": bool, "state": str | None}.
"""
states: dict[str, dict] = {}
+ # Filter out None values — HostFactory scheduler uses machineId (camelCase) while
+ # the default scheduler uses machine_id (snake_case). Callers that only try one
+ # field name will produce a list of Nones when the wrong key is used; silently
+ # dropping Nones here prevents a botocore.ParamValidationError from crashing the check.
+ instance_ids = [iid for iid in instance_ids if iid is not None]
if not instance_ids:
return states
for start in range(0, len(instance_ids), chunk_size):
chunk = instance_ids[start : start + chunk_size]
try:
- resp = ec2_client.describe_instances(InstanceIds=chunk)
+ resp = _get_ec2_client().describe_instances(InstanceIds=chunk)
except ClientError as exc: # pragma: no cover - defensive
log.warning("describe_instances failed for chunk %s: %s", chunk, exc)
continue
@@ -1181,18 +1314,28 @@ def _describe_instances_bulk(instance_ids: list[str], chunk_size: int = 100) ->
def _check_all_ec2_hosts_are_being_provisioned(status_response):
- """Verify all EC2 instances are being provisioned."""
+ """Verify all EC2 instances are being provisioned.
+
+ Handles both HostFactory (camelCase ``machineId``) and default-scheduler
+ (snake_case ``machine_id``) response formats.
+ """
machines = status_response["requests"][0]["machines"]
- instance_ids = [m.get("machine_id") for m in machines]
+ # Resolve the instance ID regardless of which scheduler produced the response.
+ # HostFactory uses machineId; the default scheduler uses machine_id.
+ instance_ids = [m.get("machine_id") or m.get("machineId") for m in machines]
states = _describe_instances_bulk(instance_ids)
for machine in machines:
- ec2_instance_id = machine.get("machine_id")
+ ec2_instance_id = machine.get("machine_id") or machine.get("machineId")
res = states.get(ec2_instance_id, {"exists": False, "state": None})
- assert res["exists"] is True
+ assert res["exists"] is True, (
+ f"Instance {ec2_instance_id!r} not found in AWS describe_instances response"
+ )
# EC2 host may still be initializing
- assert res["state"] in ["running", "pending"]
+ assert res["state"] in ["running", "pending"], (
+ f"Instance {ec2_instance_id!r} is in unexpected state {res['state']!r}"
+ )
log.debug(f"EC2 {ec2_instance_id} state: {json.dumps(res, indent=4)}")
@@ -1276,7 +1419,7 @@ def _fetch_history() -> list[dict]:
log.info(f"Calling describe_fleets to determine fleet type for {resource_id}")
fleet_desc = None
try:
- fleet_resp = ec2_client.describe_fleets(FleetIds=[resource_id])
+ fleet_resp = _get_ec2_client().describe_fleets(FleetIds=[resource_id])
fleet_desc = (fleet_resp.get("Fleets") or [None])[0]
except ClientError as exc:
log.warning("describe_fleets failed for %s: %s", resource_id, exc)
@@ -1311,7 +1454,9 @@ def _collect_fleet_instance_ids() -> list[str]:
# As a last resort, ask AWS directly
if not ids:
try:
- inst_resp = ec2_client.describe_fleet_instances(FleetId=resource_id)
+ inst_resp = _get_ec2_client().describe_fleet_instances(
+ FleetId=resource_id
+ )
for entry in inst_resp.get("ActiveInstances", []) or []:
iid = entry.get("InstanceId")
if iid:
@@ -1340,7 +1485,7 @@ def _collect_fleet_instance_ids() -> list[str]:
for start_idx in range(0, len(instance_ids), 100):
chunk = instance_ids[start_idx : start_idx + 100]
try:
- resp = ec2_client.describe_instances(InstanceIds=chunk)
+ resp = _get_ec2_client().describe_instances(InstanceIds=chunk)
except ClientError as exc:
log.warning("describe_instances failed for chunk %s: %s", chunk, exc)
continue
@@ -1389,7 +1534,7 @@ def _collect_fleet_instance_ids() -> list[str]:
params["NextToken"] = next_token
try:
- response = ec2_client.describe_fleet_history(**params)
+ response = _get_ec2_client().describe_fleet_history(**params)
except ClientError as exc:
message = str(exc).lower()
if (
@@ -1421,7 +1566,7 @@ def _collect_fleet_instance_ids() -> list[str]:
if next_token:
params["NextToken"] = next_token
- response = ec2_client.describe_spot_fleet_request_history(**params)
+ response = _get_ec2_client().describe_spot_fleet_request_history(**params)
history_records.extend(response.get("HistoryRecords", []))
next_token = response.get("NextToken")
if not next_token:
@@ -1439,7 +1584,7 @@ def _collect_fleet_instance_ids() -> list[str]:
if next_token:
params["NextToken"] = next_token
- response = asg_client.describe_scaling_activities(**params)
+ response = _get_asg_client().describe_scaling_activities(**params)
history_records.extend(response.get("Activities", []))
next_token = response.get("NextToken")
if not next_token:
@@ -1494,7 +1639,7 @@ def _collect_fleet_instance_ids() -> list[str]:
for start in range(0, len(missing_ids), 100):
chunk = missing_ids[start : start + 100]
try:
- resp = ec2_client.describe_instances(InstanceIds=chunk)
+ resp = _get_ec2_client().describe_instances(InstanceIds=chunk)
except ClientError as exc:
log.warning("describe_instances failed for chunk %s: %s", chunk, exc)
continue
@@ -1564,7 +1709,7 @@ def _collect_fleet_instance_ids() -> list[str]:
# tooling can compute request_time similarly to fleet history.
if provider_api == "ASG":
try:
- response = asg_client.describe_auto_scaling_groups(
+ response = _get_asg_client().describe_auto_scaling_groups(
AutoScalingGroupNames=[resource_id]
)
groups = response.get("AutoScalingGroups", [])
@@ -1597,11 +1742,11 @@ def _collect_fleet_instance_ids() -> list[str]:
log.info(f"Writing history to file: {history_file}")
with open(history_file, "w") as f:
json.dump(history_data, f, indent=2, default=str)
- log.info(f"✅ Resource history saved successfully to: {history_file}")
+ log.info(f"Resource history saved successfully to: {history_file}")
log.info(f"File size: {os.path.getsize(history_file)} bytes")
except Exception as exc:
- log.error(f"❌ Failed to capture resource history: {exc}", exc_info=True)
+ log.error(f"Failed to capture resource history: {exc}", exc_info=True)
try:
failure_payload = {
"resource_id": resource_id,
@@ -1620,12 +1765,12 @@ def _terminate_backing_resource(resource_id: str, provider_api: str) -> None:
try:
if provider_api == "EC2Fleet":
log.info(f"Deleting EC2Fleet: {resource_id}")
- ec2_client.delete_fleets(FleetIds=[resource_id], TerminateInstances=True)
+ _get_ec2_client().delete_fleets(FleetIds=[resource_id], TerminateInstances=True)
_wait_for_fleet_deletion(resource_id)
elif provider_api == "SpotFleet":
log.info(f"Cancelling SpotFleet: {resource_id}")
- ec2_client.cancel_spot_fleet_requests(
+ _get_ec2_client().cancel_spot_fleet_requests(
SpotFleetRequestIds=[resource_id], TerminateInstances=True
)
_wait_for_spot_fleet_deletion(resource_id)
@@ -1633,7 +1778,7 @@ def _terminate_backing_resource(resource_id: str, provider_api: str) -> None:
elif provider_api == "ASG":
log.info(f"Checking Auto Scaling Group status: {resource_id}")
try:
- response = asg_client.describe_auto_scaling_groups(
+ response = _get_asg_client().describe_auto_scaling_groups(
AutoScalingGroupNames=[resource_id]
)
groups = response.get("AutoScalingGroups", [])
@@ -1649,7 +1794,7 @@ def _terminate_backing_resource(resource_id: str, provider_api: str) -> None:
return
log.info(f"Deleting Auto Scaling Group: {resource_id}")
- asg_client.delete_auto_scaling_group(
+ _get_asg_client().delete_auto_scaling_group(
AutoScalingGroupName=resource_id, ForceDelete=True
)
_wait_for_asg_deletion(resource_id)
@@ -1684,7 +1829,7 @@ def _wait_for_fleet_deletion(fleet_id: str, timeout: int = 300) -> None:
start = time.time()
while time.time() - start < timeout:
try:
- response = ec2_client.describe_fleets(FleetIds=[fleet_id])
+ response = _get_ec2_client().describe_fleets(FleetIds=[fleet_id])
fleets = response.get("Fleets", [])
if not fleets or fleets[0]["FleetState"] in ["deleted_terminating", "deleted_running"]:
log.info(f"Fleet {fleet_id} is being deleted")
@@ -1702,7 +1847,9 @@ def _wait_for_spot_fleet_deletion(fleet_id: str, timeout: int = 300) -> None:
start = time.time()
while time.time() - start < timeout:
try:
- response = ec2_client.describe_spot_fleet_requests(SpotFleetRequestIds=[fleet_id])
+ response = _get_ec2_client().describe_spot_fleet_requests(
+ SpotFleetRequestIds=[fleet_id]
+ )
requests = response.get("SpotFleetRequestConfigs", [])
if not requests or requests[0]["SpotFleetRequestState"] in [
"cancelled_terminating",
@@ -1723,7 +1870,9 @@ def _wait_for_asg_deletion(asg_name: str, timeout: int = 300) -> None:
start = time.time()
while time.time() - start < timeout:
try:
- response = asg_client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])
+ response = _get_asg_client().describe_auto_scaling_groups(
+ AutoScalingGroupNames=[asg_name]
+ )
groups = response.get("AutoScalingGroups", [])
if not groups:
log.info(f"ASG {asg_name} deleted")
@@ -1783,11 +1932,19 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
# 1.2: Find target template
log.info("1.2: Finding target template")
template_id = test_case.get("template_id") or test_case["test_name"]
+ # Each scheduler defines its own wire format. HostFactory uses camelCase
+ # (IBM Symphony spec); the default scheduler keeps ORB's domain snake_case.
+ # Tests must match the scheduler under test — otherwise a real wire-format
+ # regression would be hidden by a permissive lookup.
+ scheduler = test_case.get("overrides", {}).get("scheduler", "default")
+ template_id_key = "templateId" if scheduler == "hostfactory" else "template_id"
+ provider_api_key = "providerApi" if scheduler == "hostfactory" else "provider_api"
+
template_json = next(
(
template
for template in templates_response["templates"]
- if template.get("template_id") == template_id
+ if template.get(template_id_key) == template_id
),
None,
)
@@ -1796,9 +1953,9 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
log.warning(f"Template {template_id} not found, using first available template")
template_json = templates_response["templates"][0]
- log.info(f"Using template: {template_json.get('template_id')}")
+ log.info(f"Using template: {template_json.get(template_id_key)}")
provider_api = (
- template_json.get("provider_api")
+ template_json.get(provider_api_key)
or test_case.get("overrides", {}).get("providerApi")
or "EC2Fleet"
)
@@ -1807,14 +1964,15 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
# 1.3: Request machines via REST API
log.info(f"1.3: Requesting {test_case['capacity_to_request']} machines")
request_response = rest_api_client.request_machines(
- template_id=template_json["template_id"],
+ template_id=template_json[template_id_key],
machine_count=test_case["capacity_to_request"],
)
log.debug(f"Request response: {json.dumps(request_response, indent=2)}")
# 1.4: Validate request response
log.info("1.4: Validating request response")
- request_id = request_response.get("request_id")
+ request_id_key = "requestId" if scheduler == "hostfactory" else "request_id"
+ request_id = request_response.get(request_id_key)
if not request_id:
pytest.fail(f"Request ID missing in response: {request_response}")
assert REQUEST_ID_RE.match(request_id), (
@@ -1861,7 +2019,11 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
requests_list = status_response.get("requests") or []
first_request = requests_list[0] if requests_list else {}
machines = first_request.get("machines") or []
- machine_ids_tmp = [m.get("machine_id") for m in machines if m.get("machine_id")]
+ machine_ids_tmp = [
+ m.get("machineId") or m.get("machine_id")
+ for m in machines
+ if m.get("machineId") or m.get("machine_id")
+ ]
if machine_ids_tmp:
history_resource_id = _get_resource_id_from_instance(
machine_ids_tmp[0], provider_api
@@ -1919,8 +2081,12 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
if abis_requested:
log.info("2.6: Verifying ABIS configuration")
first_machine = status_response["requests"][0]["machines"][0]
- instance_id = first_machine.get("machine_id")
- assert instance_id is not None, "machine_id missing from first machine"
+ # HostFactory scheduler serialises the instance ID as camelCase "machineId";
+ # the default scheduler uses snake_case "machine_id". Try both.
+ instance_id = first_machine.get("machineId") or first_machine.get("machine_id")
+ assert instance_id is not None, (
+ f"machine_id/machineId missing from first machine: {list(first_machine.keys())}"
+ )
verify_abis_enabled_for_instance(instance_id)
log.info("ABIS verification PASSED")
@@ -1929,7 +2095,12 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
# 3.1: Extract instance IDs
log.info("3.1: Extracting instance IDs")
- machine_ids = [machine["machine_id"] for machine in status_response["requests"][0]["machines"]]
+ # HostFactory scheduler serialises instance IDs as camelCase "machineId";
+ # the default scheduler uses snake_case "machine_id". Support both.
+ machine_ids = [
+ machine.get("machineId") or machine.get("machine_id")
+ for machine in status_response["requests"][0]["machines"]
+ ]
log.info(f"Machine IDs to return: {machine_ids}")
# Determine resource ID
@@ -1953,7 +2124,8 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
return_response = rest_api_client.return_machines(machine_ids)
log.debug(f"Return response: {json.dumps(return_response, indent=2)}")
- return_request_id = return_response.get("request_id")
+ # HostFactory scheduler returns "requestId" (camelCase); default uses "request_id".
+ return_request_id = return_response.get("request_id") or return_response.get("requestId")
if not return_request_id:
log.warning(f"Return request ID missing in response: {return_response}")
else:
@@ -2029,13 +2201,13 @@ def test_rest_api_control_loop(rest_api_client, setup_rest_api_environment, test
)
if not cleanup_verified:
- log.error("⚠️ Cleanup verification failed - some resources may still exist")
+ log.error("Cleanup verification failed - some resources may still exist")
for instance_id in machine_ids:
state_info = get_instance_state(instance_id)
if state_info["exists"]:
log.error(f"Instance {instance_id} still exists in state: {state_info['state']}")
else:
- log.info("✅ All resources successfully cleaned up")
+ log.info("All resources successfully cleaned up")
log.info("=" * 80)
log.info(f"REST API test completed: {test_case['test_name']}")
@@ -2062,8 +2234,10 @@ def test_rest_api_unknown_template_returns_error(setup_rest_api_environment):
client.request_machines("NonExistent-Template-XYZ", 1)
pytest.fail("Expected HTTPError for unknown template but no exception was raised")
except _requests.HTTPError as exc:
- assert exc.response.status_code >= 400, (
- f"Expected 4xx for unknown template, got {exc.response.status_code}"
+ response = exc.response
+ assert response is not None, "HTTPError raised with no response attached"
+ assert response.status_code >= 400, (
+ f"Expected 4xx for unknown template, got {response.status_code}"
)
except Exception as exc:
# Any other exception is also acceptable — server rejected the request
diff --git a/tests/onaws/test_sdk_onaws.py b/tests/providers/aws/live/test_sdk_onaws.py
similarity index 79%
rename from tests/onaws/test_sdk_onaws.py
rename to tests/providers/aws/live/test_sdk_onaws.py
index 22b589ed2..5bf051e6a 100644
--- a/tests/onaws/test_sdk_onaws.py
+++ b/tests/providers/aws/live/test_sdk_onaws.py
@@ -12,17 +12,17 @@
import boto3.session
import pytest
-from tests.onaws import scenarios
-from tests.onaws.cleanup_helpers import (
+from tests.providers.aws.live import scenarios
+from tests.providers.aws.live.cleanup_helpers import (
cleanup_launch_templates_for_request,
get_machine_ids_from_ec2 as _get_machine_ids_from_ec2_helper,
wait_for_instances_terminated,
)
-from tests.onaws.scenarios import CUSTOM_TEST_CASES
-from tests.onaws.template_processor import TemplateProcessor
+from tests.providers.aws.live.scenarios import CUSTOM_TEST_CASES
+from tests.providers.aws.live.template_processor import TemplateProcessor
try:
- from tests.onaws.scenarios_sdk import ( # type: ignore[import]
+ from tests.providers.aws.live.scenarios_sdk import ( # type: ignore[import]
SDK_RUN_CUSTOM_CASES,
SDK_RUN_DEFAULT_COMBINATIONS,
SDK_TIMEOUTS,
@@ -34,7 +34,7 @@
# Import AWS validation helpers from test_onaws (guarded for env/creds issues)
try:
- from tests.onaws.test_onaws import (
+ from tests.providers.aws.live.test_onaws import (
_check_all_ec2_hosts_are_being_terminated,
get_instance_state,
)
@@ -56,14 +56,46 @@
os.environ["LOG_DESTINATION"] = "file"
os.environ.setdefault("AWS_REGION", "eu-west-1")
-_boto_session = boto3.session.Session()
-_ec2_region = (
- os.environ.get("AWS_REGION")
- or os.environ.get("AWS_DEFAULT_REGION")
- or _boto_session.region_name
- or "eu-west-1"
-)
-ec2_client = _boto_session.client("ec2", region_name=_ec2_region)
+_ec2_client = None
+
+
+def _get_boto_profile_and_region() -> tuple[str | None, str]:
+ """Read AWS profile and region from ORB_CONFIG_DIR config."""
+ import json as _json
+
+ profile = None
+ region = None
+ config_dir = os.environ.get("ORB_CONFIG_DIR")
+ if config_dir:
+ try:
+ config_path = os.path.join(config_dir, "config.json")
+ with open(config_path) as _f:
+ config = _json.load(_f)
+ providers = config.get("provider", {}).get("providers", [])
+ if providers:
+ provider_cfg = providers[0].get("config", {})
+ profile = provider_cfg.get("profile")
+ region = provider_cfg.get("region")
+ except Exception:
+ pass
+ region = (
+ region
+ or os.environ.get("AWS_REGION")
+ or os.environ.get("AWS_DEFAULT_REGION")
+ or "eu-west-1"
+ )
+ return profile, region
+
+
+def _get_ec2_client():
+ global _ec2_client
+ if _ec2_client is None:
+ _profile, _region = _get_boto_profile_and_region()
+ _ec2_client = boto3.session.Session(profile_name=_profile, region_name=_region).client(
+ "ec2", region_name=_region
+ )
+ return _ec2_client
+
log = logging.getLogger("sdk_test")
log.setLevel(logging.DEBUG)
@@ -115,15 +147,16 @@ def setup_sdk_test(request, test_session_id):
"AWS_PROVIDER_LOG_DIR",
"HF_LOGDIR",
]
- os.environ["ORB_CONFIG_DIR"] = str(test_config_dir)
- os.environ["HF_PROVIDER_CONFDIR"] = str(test_config_dir)
+ config_dir = test_config_dir / "config"
+ os.environ["ORB_CONFIG_DIR"] = str(config_dir)
+ os.environ["HF_PROVIDER_CONFDIR"] = str(config_dir)
os.environ["HF_PROVIDER_LOGDIR"] = str(test_config_dir / "logs")
os.environ["HF_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["DEFAULT_PROVIDER_WORKDIR"] = str(test_config_dir / "work")
os.environ["AWS_PROVIDER_LOG_DIR"] = str(test_config_dir / "logs")
os.environ["HF_LOGDIR"] = str(test_config_dir / "logs")
- config_path = str(test_config_dir / "config.json")
+ config_path = str(config_dir / "config.json")
_tracked_request_ids: list[str] = []
@@ -181,14 +214,14 @@ async def _cleanup() -> None:
for req_id in _tracked_request_ids
for mid in _get_machine_ids_from_ec2(req_id)
],
- ec2_client,
+ _get_ec2_client(),
)
except Exception as exc:
log.warning("Fixture teardown: wait_for_instances_terminated failed: %s", exc)
for req_id in _tracked_request_ids:
try:
- cleanup_launch_templates_for_request(req_id, ec2_client)
+ cleanup_launch_templates_for_request(req_id, _get_ec2_client())
except Exception as exc:
log.warning(
"Fixture teardown: cleanup_launch_templates failed for %s: %s", req_id, exc
@@ -214,7 +247,7 @@ async def _cleanup() -> None:
def _get_machine_ids_from_ec2(request_id: str) -> list[str]:
- return _get_machine_ids_from_ec2_helper(request_id, ec2_client)
+ return _get_machine_ids_from_ec2_helper(request_id, _get_ec2_client())
from tests.shared.response_helpers import (
@@ -309,17 +342,41 @@ async def _run_full_cycle(sdk, test_case: dict, tracked_request_ids: list[str])
)
machine_ids = _extract_machine_ids(status_response)
- assert len(machine_ids) == capacity, (
- f"Expected {capacity} machines, got {len(machine_ids)}: {machine_ids}"
+
+ # Capacity-unit fulfilment check from provider response fields.
+ # target_units / fulfilled_units reflect capacity units for weighted fleets
+ # and instance count for unweighted templates (1 unit == 1 instance).
+ _req0 = status_response.get("requests", [{}])[0] if isinstance(status_response, dict) else {}
+ target_units = _req0["target_units"] if _req0.get("target_units") is not None else capacity
+ fulfilled_units = (
+ _req0["fulfilled_units"] if _req0.get("fulfilled_units") is not None else len(machine_ids)
+ )
+ assert fulfilled_units >= target_units, (
+ f"Fleet not fully fulfilled: fulfilled={fulfilled_units}, target={target_units}"
)
+ # Template-aware instance count sanity check.
+ if scenarios.template_uses_weighted_capacity(test_case):
+ assert len(machine_ids) >= 1, (
+ f"Expected at least 1 machine (weighted template), got: {machine_ids}"
+ )
+ else:
+ assert len(machine_ids) == capacity, (
+ f"Expected {capacity} machines (unweighted template), got {len(machine_ids)}: {machine_ids}"
+ )
+
for machine_id in machine_ids:
state = get_instance_state(machine_id)
assert state["exists"], f"Instance {machine_id} not found in AWS"
assert state["state"] in ("running", "pending"), (
f"Instance {machine_id} in unexpected state: {state['state']}"
)
- log.info("All %d instances provisioned: %s", capacity, machine_ids)
+ log.info(
+ "Provisioned %d instance(s) for capacity %d: %s",
+ len(machine_ids),
+ capacity,
+ machine_ids,
+ )
# 4. Return machines
return_result = await sdk.create_return_request(machine_ids=machine_ids) # type: ignore[attr-defined]
@@ -331,26 +388,19 @@ async def _run_full_cycle(sdk, test_case: dict, tracked_request_ids: list[str])
else getattr(return_result, "request_id", None)
)
- # 5. Poll return completion
+ # 5. Poll return completion via get_request_status(return_request_id).
+ # The HostFactory getReturnRequests wire format flattens per-machine items
+ # without a per-request status, so the right way to ask "is this return
+ # complete?" under any scheduler is get_request_status(return_id).
if return_request_id:
deadline = time.time() + SDK_TIMEOUTS["return_completion"]
+ terminal = {"complete", "complete_with_error", "failed", "cancelled", "timeout"}
while True:
- ret_status = await sdk.list_return_requests() # type: ignore[attr-defined]
- requests = (
- ret_status.get("requests", []) if isinstance(ret_status, dict) else ret_status or []
- )
- done = False
- for req in requests:
- rid = (
- req.get("requestId") or req.get("request_id")
- if isinstance(req, dict)
- else getattr(req, "request_id", None)
- )
- s = req.get("status") if isinstance(req, dict) else getattr(req, "status", None)
- if rid == return_request_id and s == "complete":
- done = True
- break
- if done:
+ status_response = await sdk.get_request_status(request_id=return_request_id) # type: ignore[attr-defined]
+ status = _extract_request_status(status_response)
+ if status in terminal:
+ if status != "complete":
+ pytest.fail(f"Return request ended with non-success status: {status}")
break
if time.time() > deadline:
pytest.fail("Timed out waiting for return request to complete")
@@ -359,7 +409,7 @@ async def _run_full_cycle(sdk, test_case: dict, tracked_request_ids: list[str])
# 6. Assert AWS-side termination
all_terminated = _check_all_ec2_hosts_are_being_terminated(machine_ids)
assert all_terminated, f"Some instances not terminated: {machine_ids}"
- log.info("All %d instances terminated", capacity)
+ log.info("All %d instance(s) terminated", len(machine_ids))
# ---------------------------------------------------------------------------
diff --git a/tests/unit/providers/aws/cli/__init__.py b/tests/providers/aws/moto/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/cli/__init__.py
rename to tests/providers/aws/moto/__init__.py
diff --git a/tests/providers/aws/moto/conftest.py b/tests/providers/aws/moto/conftest.py
new file mode 100644
index 000000000..048b1a02a
--- /dev/null
+++ b/tests/providers/aws/moto/conftest.py
@@ -0,0 +1,58 @@
+"""Moto-specific conftest for full-pipeline mocked AWS tests.
+
+Applies the moto compat patch to every test in this subtree and re-exports
+helpers from the parent AWS conftest so that imports such as:
+
+ from tests.providers.aws.moto.conftest import _inject_moto_factory
+
+continue to work without modification.
+"""
+
+import pytest
+
+# Re-export helpers so existing imports remain valid
+from tests.providers.aws.conftest import ( # noqa: F401
+ REGION,
+ _inject_moto_factory,
+ _make_config_port,
+ _make_launch_template_manager,
+ _make_logger,
+ _make_moto_aws_client,
+ make_asg_handler,
+ make_aws_template,
+ make_ec2_fleet_handler,
+ make_patch_moto_compat,
+ make_request,
+ make_run_instances_handler,
+ make_spot_fleet_handler,
+)
+
+__all__ = [
+ "REGION",
+ "_inject_moto_factory",
+ "_make_config_port",
+ "_make_launch_template_manager",
+ "_make_logger",
+ "_make_moto_aws_client",
+ "make_asg_handler",
+ "make_aws_template",
+ "make_ec2_fleet_handler",
+ "make_patch_moto_compat",
+ "make_request",
+ "make_run_instances_handler",
+ "make_spot_fleet_handler",
+]
+
+
+@pytest.fixture(autouse=True)
+def patch_moto_compat():
+ """Patch moto-incompatible behaviours for all moto/ tests.
+
+ 1. AWSImageResolutionService.is_resolution_needed -> False
+ Prevents SSM path resolution which moto cannot fulfil.
+
+ 2. AWSProvisioningAdapter._provision_via_handlers synthesises instances
+ from instance_ids so the orchestration loop sees fulfilled_count > 0.
+ """
+ with make_patch_moto_compat():
+ yield
diff --git a/tests/onmoto/test_asg_price_types.py b/tests/providers/aws/moto/test_asg_price_types.py
similarity index 99%
rename from tests/onmoto/test_asg_price_types.py
rename to tests/providers/aws/moto/test_asg_price_types.py
index 3a7b0cfb9..337a2f730 100644
--- a/tests/onmoto/test_asg_price_types.py
+++ b/tests/providers/aws/moto/test_asg_price_types.py
@@ -375,9 +375,9 @@ def test_check_status_asg_in_deleting_state_returns_empty(self, handler, vpc, as
asg_client.delete_auto_scaling_group(AutoScalingGroupName=asg_name, ForceDelete=True)
status_request = _make_request(request_id="asg-status-001", resource_ids=[asg_name])
- status = handler.check_hosts_status(status_request)
+ result = handler.check_hosts_status(status_request)
- assert status == []
+ assert result.instances == []
class TestASGTags:
diff --git a/tests/onmoto/test_cleanup_e2e.py b/tests/providers/aws/moto/test_cleanup_e2e.py
similarity index 100%
rename from tests/onmoto/test_cleanup_e2e.py
rename to tests/providers/aws/moto/test_cleanup_e2e.py
diff --git a/tests/onmoto/test_cli_onmoto.py b/tests/providers/aws/moto/test_cli_onmoto.py
similarity index 99%
rename from tests/onmoto/test_cli_onmoto.py
rename to tests/providers/aws/moto/test_cli_onmoto.py
index 48894b1b8..2d0119e4e 100644
--- a/tests/onmoto/test_cli_onmoto.py
+++ b/tests/providers/aws/moto/test_cli_onmoto.py
@@ -25,7 +25,11 @@
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
-from tests.onmoto.conftest import _inject_moto_factory, _make_logger, _make_moto_aws_client
+from tests.providers.aws.moto.conftest import (
+ _inject_moto_factory,
+ _make_logger,
+ _make_moto_aws_client,
+)
from tests.shared.constants import REQUEST_ID_RE
from tests.shared.scenarios import TestScenario, get_smoke_scenarios
diff --git a/tests/onmoto/test_client_template_format.py b/tests/providers/aws/moto/test_client_template_format.py
similarity index 100%
rename from tests/onmoto/test_client_template_format.py
rename to tests/providers/aws/moto/test_client_template_format.py
diff --git a/tests/onmoto/test_config_driven_provision.py b/tests/providers/aws/moto/test_config_driven_provision.py
similarity index 100%
rename from tests/onmoto/test_config_driven_provision.py
rename to tests/providers/aws/moto/test_config_driven_provision.py
diff --git a/tests/onmoto/test_cqrs_control_loop.py b/tests/providers/aws/moto/test_cqrs_control_loop.py
similarity index 100%
rename from tests/onmoto/test_cqrs_control_loop.py
rename to tests/providers/aws/moto/test_cqrs_control_loop.py
diff --git a/tests/onmoto/test_cross_cutting.py b/tests/providers/aws/moto/test_cross_cutting.py
similarity index 100%
rename from tests/onmoto/test_cross_cutting.py
rename to tests/providers/aws/moto/test_cross_cutting.py
diff --git a/tests/onmoto/test_di_wiring.py b/tests/providers/aws/moto/test_di_wiring.py
similarity index 98%
rename from tests/onmoto/test_di_wiring.py
rename to tests/providers/aws/moto/test_di_wiring.py
index e4e6634e2..6839ed6d1 100644
--- a/tests/onmoto/test_di_wiring.py
+++ b/tests/providers/aws/moto/test_di_wiring.py
@@ -200,7 +200,7 @@ def test_aws_template_accepts_allocation_strategy_as_string():
assert template.allocation_strategy_on_demand is not None
# Must be the enum, not a raw string
- from orb.providers.aws.domain.template.value_objects import AWSAllocationStrategy
+ from orb.providers.aws.value_objects import AWSAllocationStrategy
assert isinstance(template.allocation_strategy_on_demand, AWSAllocationStrategy)
@@ -213,7 +213,7 @@ def test_aws_template_accepts_allocation_strategy_as_string():
def test_aws_template_accepts_allocation_strategy_as_enum():
"""AWSTemplate accepts an AWSAllocationStrategy object directly (no coercion needed)."""
from orb.providers.aws.domain.template.aws_template_aggregate import AWSTemplate
- from orb.providers.aws.domain.template.value_objects import AWSAllocationStrategy
+ from orb.providers.aws.value_objects import AWSAllocationStrategy
strategy = AWSAllocationStrategy.from_string("lowestPrice")
template = AWSTemplate(
diff --git a/tests/onmoto/test_ec2fleet_price_types.py b/tests/providers/aws/moto/test_ec2fleet_price_types.py
similarity index 99%
rename from tests/onmoto/test_ec2fleet_price_types.py
rename to tests/providers/aws/moto/test_ec2fleet_price_types.py
index 9be241101..ccb3e7bc1 100644
--- a/tests/onmoto/test_ec2fleet_price_types.py
+++ b/tests/providers/aws/moto/test_ec2fleet_price_types.py
@@ -23,7 +23,7 @@
from orb.providers.aws.domain.template.aws_template_aggregate import AWSTemplate
from orb.providers.aws.infrastructure.handlers.ec2_fleet.handler import EC2FleetHandler
from orb.providers.aws.utilities.aws_operations import AWSOperations
-from tests.onmoto.conftest import (
+from tests.providers.aws.moto.conftest import (
_make_config_port,
_make_launch_template_manager,
_make_logger,
@@ -378,7 +378,7 @@ def test_check_status_deleted_fleet_returns_empty(
)
result = handler.check_hosts_status(status_request)
- assert result == []
+ assert result.instances == []
# ---------------------------------------------------------------------------
diff --git a/tests/onmoto/test_error_paths.py b/tests/providers/aws/moto/test_error_paths.py
similarity index 95%
rename from tests/onmoto/test_error_paths.py
rename to tests/providers/aws/moto/test_error_paths.py
index 7b5f18bc9..74fe4a993 100644
--- a/tests/onmoto/test_error_paths.py
+++ b/tests/providers/aws/moto/test_error_paths.py
@@ -13,7 +13,7 @@
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
from orb.providers.aws.exceptions.aws_exceptions import AWSValidationError
-from tests.onmoto.conftest import (
+from tests.providers.aws.moto.conftest import (
_make_config_port,
_make_logger,
_make_moto_aws_client as _make_aws_client,
@@ -204,9 +204,11 @@ def test_release_nonexistent_instances_does_not_crash(self, asg_handler):
def test_check_hosts_status_nonexistent_asg_returns_empty(self, asg_handler):
"""check_hosts_status for a non-existent ASG returns [] without crashing."""
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
request = make_request(resource_ids=["asg-totally-fake-does-not-exist"])
result = asg_handler.check_hosts_status(request)
- assert isinstance(result, list)
+ assert isinstance(result, CheckHostsStatusResult)
def test_run_instances_invalid_ami_propagates_or_returns_failure(
self, run_instances_handler, moto_vpc_resources
@@ -310,7 +312,7 @@ def handler(self, moto_aws):
aws_client = _make_aws_client(region=REGION)
logger = _make_logger()
config_port = _make_config_port(prefix="")
- from tests.onmoto.conftest import make_ec2_fleet_handler
+ from tests.providers.aws.moto.conftest import make_ec2_fleet_handler
return make_ec2_fleet_handler(aws_client, logger, config_port)
@@ -348,7 +350,7 @@ def test_check_hosts_status_no_resource_ids_raises(self, handler):
handler.check_hosts_status(request)
def test_check_hosts_status_after_acquire_maintain(self, handler, vpc):
- from tests.onmoto.conftest import make_ec2_fleet_handler
+ from tests.providers.aws.moto.conftest import make_ec2_fleet_handler
aws_client = _make_aws_client(region=REGION)
h = make_ec2_fleet_handler(aws_client, _make_logger(), _make_config_port())
@@ -359,9 +361,11 @@ def test_check_hosts_status_after_acquire_maintain(self, handler, vpc):
acquire_result = h.acquire_hosts(request, template)
fleet_id = acquire_result["resource_ids"][0] # type: ignore[index]
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
status_request = make_request(resource_ids=[fleet_id], metadata={"fleet_type": "maintain"})
result = h.check_hosts_status(status_request)
- assert isinstance(result, list)
+ assert isinstance(result, CheckHostsStatusResult)
def test_release_hosts_with_resource_mapping(self, handler, vpc):
template = make_aws_template(
@@ -388,7 +392,7 @@ def test_release_hosts_with_resource_mapping(self, handler, vpc):
def _make_spot_handler_patched(moto_aws):
- from tests.onmoto.conftest import make_spot_fleet_handler
+ from tests.providers.aws.moto.conftest import make_spot_fleet_handler
aws_client = _make_aws_client(region=REGION)
h = make_spot_fleet_handler(aws_client, _make_logger(), _make_config_port())
@@ -437,10 +441,12 @@ def test_acquire_fleet_missing_fleet_type_returns_failure(self, moto_aws, vpc):
h.acquire_hosts(request, bad_template)
def test_check_hosts_status_unknown_fleet_returns_empty(self, moto_aws):
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
h = _make_spot_handler_patched(moto_aws)
request = make_request(resource_ids=["sfr-00000000-0000-0000-0000-000000000000"])
result = h.check_hosts_status(request)
- assert isinstance(result, list)
+ assert isinstance(result, CheckHostsStatusResult)
def test_release_hosts_with_resource_mapping(self, moto_aws, vpc):
h = _make_spot_handler_patched(moto_aws)
@@ -476,7 +482,7 @@ class TestRunInstancesHandlerEdgeCases:
@pytest.fixture
def handler(self, moto_aws):
aws_client = _make_aws_client(region=REGION)
- from tests.onmoto.conftest import make_run_instances_handler
+ from tests.providers.aws.moto.conftest import make_run_instances_handler
return make_run_instances_handler(aws_client, _make_logger(), _make_config_port())
@@ -526,13 +532,13 @@ def test_check_hosts_status_falls_back_to_resource_ids(self, handler, vpc):
)
result = handler.check_hosts_status(status_request)
- assert isinstance(result, list)
- assert len(result) == 1
+ assert isinstance(result.instances, list)
+ assert len(result.instances) == 1
def test_check_hosts_status_no_ids_returns_empty(self, handler):
request = make_request(resource_ids=[], provider_data={})
result = handler.check_hosts_status(request)
- assert result == []
+ assert result.instances == []
def test_check_hosts_status_multiple_instances(self, handler, vpc):
template = make_aws_template(subnet_id=vpc["subnet_id"], sg_id=vpc["sg_id"])
@@ -549,8 +555,8 @@ def test_check_hosts_status_multiple_instances(self, handler, vpc):
)
result = handler.check_hosts_status(status_request)
- assert len(result) == len(instance_ids)
- assert {r["instance_id"] for r in result} == set(instance_ids)
+ assert len(result.instances) == len(instance_ids)
+ assert {r["instance_id"] for r in result.instances} == set(instance_ids)
def test_release_hosts_idempotent_on_already_terminated(self, handler, vpc):
template = make_aws_template(subnet_id=vpc["subnet_id"], sg_id=vpc["sg_id"])
diff --git a/tests/onmoto/test_hf_contract.py b/tests/providers/aws/moto/test_hf_contract.py
similarity index 98%
rename from tests/onmoto/test_hf_contract.py
rename to tests/providers/aws/moto/test_hf_contract.py
index 41209eb0e..b90e7ff42 100644
--- a/tests/onmoto/test_hf_contract.py
+++ b/tests/providers/aws/moto/test_hf_contract.py
@@ -18,7 +18,7 @@
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
-from tests.onaws.plugin_io_schemas import (
+from tests.providers.aws.live.plugin_io_schemas import (
expected_get_available_templates_schema_default,
expected_get_available_templates_schema_hostfactory,
expected_request_machines_schema_default,
@@ -202,7 +202,8 @@ def _build_request_dto_from_run_instances(
resource_ids=[reservation_id],
provider_data={"instance_ids": instance_ids, "reservation_id": reservation_id},
)
- machine_data_list = handler.check_hosts_status(status_request)
+ check_result = handler.check_hosts_status(status_request)
+ machine_data_list = check_result.instances
# Build MachineReferenceDTOs from raw machine dicts returned by check_hosts_status
machine_refs = []
@@ -488,7 +489,8 @@ def test_default_status_schema_with_run_instances_machines(
resource_ids=[reservation_id],
provider_data={"instance_ids": instance_ids, "reservation_id": reservation_id},
)
- machine_data_list = handler.check_hosts_status(status_request)
+ check_result = handler.check_hosts_status(status_request)
+ machine_data_list = check_result.instances
machines = []
for m in machine_data_list:
diff --git a/tests/onmoto/test_init_discovery_onmoto.py b/tests/providers/aws/moto/test_init_discovery_onmoto.py
similarity index 100%
rename from tests/onmoto/test_init_discovery_onmoto.py
rename to tests/providers/aws/moto/test_init_discovery_onmoto.py
diff --git a/tests/onmoto/test_launch_template_existing.py b/tests/providers/aws/moto/test_launch_template_existing.py
similarity index 100%
rename from tests/onmoto/test_launch_template_existing.py
rename to tests/providers/aws/moto/test_launch_template_existing.py
diff --git a/tests/onmoto/test_mcp_onmoto.py b/tests/providers/aws/moto/test_mcp_onmoto.py
similarity index 99%
rename from tests/onmoto/test_mcp_onmoto.py
rename to tests/providers/aws/moto/test_mcp_onmoto.py
index 81149aad5..cd75b703e 100644
--- a/tests/onmoto/test_mcp_onmoto.py
+++ b/tests/providers/aws/moto/test_mcp_onmoto.py
@@ -24,7 +24,11 @@
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
-from tests.onmoto.conftest import _inject_moto_factory, _make_logger, _make_moto_aws_client
+from tests.providers.aws.moto.conftest import (
+ _inject_moto_factory,
+ _make_logger,
+ _make_moto_aws_client,
+)
from tests.shared.constants import REQUEST_ID_RE
from tests.shared.scenarios import TestScenario, get_smoke_scenarios
diff --git a/tests/onmoto/test_multi_resource_onmoto.py b/tests/providers/aws/moto/test_multi_resource_onmoto.py
similarity index 100%
rename from tests/onmoto/test_multi_resource_onmoto.py
rename to tests/providers/aws/moto/test_multi_resource_onmoto.py
diff --git a/tests/onmoto/test_partial_return.py b/tests/providers/aws/moto/test_partial_return.py
similarity index 99%
rename from tests/onmoto/test_partial_return.py
rename to tests/providers/aws/moto/test_partial_return.py
index 1a67f5747..4f990a02a 100644
--- a/tests/onmoto/test_partial_return.py
+++ b/tests/providers/aws/moto/test_partial_return.py
@@ -272,9 +272,9 @@ def test_check_status_after_partial_return_reflects_mixed_states(self, acquired_
)
result = handler.check_hosts_status(status_request)
- assert len(result) == 3
+ assert len(result.instances) == 3
- statuses = {entry["instance_id"]: entry["status"] for entry in result}
+ statuses = {entry["instance_id"]: entry["status"] for entry in result.instances}
terminated_entry = statuses[instance_ids[0]]
assert terminated_entry in ("terminated", "shutting-down", "stopping")
diff --git a/tests/onmoto/test_provision_lifecycle.py b/tests/providers/aws/moto/test_provision_lifecycle.py
similarity index 97%
rename from tests/onmoto/test_provision_lifecycle.py
rename to tests/providers/aws/moto/test_provision_lifecycle.py
index 8f6af26b4..b268f2566 100644
--- a/tests/onmoto/test_provision_lifecycle.py
+++ b/tests/providers/aws/moto/test_provision_lifecycle.py
@@ -316,11 +316,13 @@ def test_check_status_after_acquire(self, factory, subnet_id, sg_id):
acquire_result = handler.acquire_hosts(request, template)
asg_name = acquire_result["resource_ids"][0]
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
status_request = _make_request(resource_ids=[asg_name])
result = handler.check_hosts_status(status_request)
# moto does not spin up ASG instances automatically
- assert isinstance(result, list)
+ assert isinstance(result, CheckHostsStatusResult)
def test_release_after_acquire(self, factory, subnet_id, sg_id, autoscaling_client):
"""release_hosts with empty machine_ids does not raise after acquire."""
@@ -375,10 +377,12 @@ def test_check_status_after_acquire(self, factory, subnet_id, sg_id):
acquire_result = handler.acquire_hosts(request, template)
fleet_id = acquire_result["resource_ids"][0]
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
status_request = _make_request(resource_ids=[fleet_id])
result = handler.check_hosts_status(status_request)
- assert isinstance(result, list)
+ assert isinstance(result, CheckHostsStatusResult)
def test_release_after_acquire(self, factory, subnet_id, sg_id):
"""release_hosts with empty machine_ids does not raise."""
@@ -432,10 +436,12 @@ def test_check_status_after_acquire(self, factory, subnet_id, sg_id):
acquire_result = handler.acquire_hosts(request, template)
fleet_id = acquire_result["resource_ids"][0]
+ from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
status_request = _make_request(resource_ids=[fleet_id])
result = handler.check_hosts_status(status_request)
- assert isinstance(result, list)
+ assert isinstance(result, CheckHostsStatusResult)
def test_release_after_acquire(self, factory, subnet_id, sg_id):
"""release_hosts with empty machine_ids does not raise."""
@@ -500,8 +506,8 @@ def test_check_status_returns_instance_data(self, factory, subnet_id, sg_id):
)
result = handler.check_hosts_status(status_request)
- assert len(result) == len(instance_ids)
- assert result[0]["instance_id"] == instance_ids[0]
+ assert len(result.instances) == len(instance_ids)
+ assert result.instances[0]["instance_id"] == instance_ids[0]
def test_release_terminates_instances(self, factory, subnet_id, sg_id, ec2_client):
"""release_hosts terminates the launched instances."""
@@ -542,7 +548,7 @@ def test_full_lifecycle(self, factory, subnet_id, sg_id, ec2_client):
provider_data={"instance_ids": instance_ids, "reservation_id": reservation_id},
)
status = handler.check_hosts_status(status_request)
- assert len(status) >= 1
+ assert len(status.instances) >= 1
# Release
handler.release_hosts(instance_ids)
diff --git a/tests/onmoto/test_rest_api_onmoto.py b/tests/providers/aws/moto/test_rest_api_onmoto.py
similarity index 99%
rename from tests/onmoto/test_rest_api_onmoto.py
rename to tests/providers/aws/moto/test_rest_api_onmoto.py
index b977cf184..fdc6d839c 100644
--- a/tests/onmoto/test_rest_api_onmoto.py
+++ b/tests/providers/aws/moto/test_rest_api_onmoto.py
@@ -19,7 +19,11 @@
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
-from tests.onmoto.conftest import _inject_moto_factory, _make_logger, _make_moto_aws_client
+from tests.providers.aws.moto.conftest import (
+ _inject_moto_factory,
+ _make_logger,
+ _make_moto_aws_client,
+)
from tests.shared.constants import REQUEST_ID_RE
from tests.shared.scenarios import TestScenario, get_smoke_scenarios
diff --git a/tests/onmoto/test_return_flow.py b/tests/providers/aws/moto/test_return_flow.py
similarity index 100%
rename from tests/onmoto/test_return_flow.py
rename to tests/providers/aws/moto/test_return_flow.py
diff --git a/tests/onmoto/test_run_instances_gaps.py b/tests/providers/aws/moto/test_run_instances_gaps.py
similarity index 97%
rename from tests/onmoto/test_run_instances_gaps.py
rename to tests/providers/aws/moto/test_run_instances_gaps.py
index 661324834..76b99abfc 100644
--- a/tests/onmoto/test_run_instances_gaps.py
+++ b/tests/providers/aws/moto/test_run_instances_gaps.py
@@ -325,10 +325,10 @@ def test_check_status_instance_in_stopped_state(
resource_ids=[reservation_id],
provider_data={"instance_ids": instance_ids, "reservation_id": reservation_id},
)
- status = handler.check_hosts_status(status_request)
+ result = handler.check_hosts_status(status_request)
- assert len(status) == len(instance_ids)
- assert status[0]["instance_id"] == instance_ids[0]
+ assert len(result.instances) == len(instance_ids)
+ assert result.instances[0]["instance_id"] == instance_ids[0]
def test_check_status_instance_in_terminated_state(
self, handler: RunInstancesHandler, subnet_id: str, sg_id: str, ec2: Any
@@ -348,10 +348,10 @@ def test_check_status_instance_in_terminated_state(
resource_ids=[reservation_id],
provider_data={"instance_ids": instance_ids, "reservation_id": reservation_id},
)
- status = handler.check_hosts_status(status_request)
+ result = handler.check_hosts_status(status_request)
- assert len(status) == len(instance_ids)
- terminated_statuses = {entry["status"] for entry in status}
+ assert len(result.instances) == len(instance_ids)
+ terminated_statuses = {entry["status"] for entry in result.instances}
assert terminated_statuses & {"terminated", "shutting-down", "stopping"}
def test_release_partial_then_full_two_step(
diff --git a/tests/onmoto/test_sdk_default_scheduler.py b/tests/providers/aws/moto/test_sdk_default_scheduler.py
similarity index 99%
rename from tests/onmoto/test_sdk_default_scheduler.py
rename to tests/providers/aws/moto/test_sdk_default_scheduler.py
index dec3ebb5c..e2fab6560 100644
--- a/tests/onmoto/test_sdk_default_scheduler.py
+++ b/tests/providers/aws/moto/test_sdk_default_scheduler.py
@@ -163,7 +163,7 @@ def orb_config_dir_default(tmp_path, moto_vpc_resources):
# Generate snake_case templates via the default scheduler strategy
try:
- from tests.onaws.template_processor import TemplateProcessor
+ from tests.providers.aws.live.template_processor import TemplateProcessor
templates_data = TemplateProcessor.generate_templates_programmatically("default")
except Exception as exc:
diff --git a/tests/onmoto/test_sdk_onmoto.py b/tests/providers/aws/moto/test_sdk_onmoto.py
similarity index 99%
rename from tests/onmoto/test_sdk_onmoto.py
rename to tests/providers/aws/moto/test_sdk_onmoto.py
index c940eec77..edc1a59be 100644
--- a/tests/onmoto/test_sdk_onmoto.py
+++ b/tests/providers/aws/moto/test_sdk_onmoto.py
@@ -20,7 +20,11 @@
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
-from tests.onmoto.conftest import _inject_moto_factory, _make_logger, _make_moto_aws_client
+from tests.providers.aws.moto.conftest import (
+ _inject_moto_factory,
+ _make_logger,
+ _make_moto_aws_client,
+)
from tests.shared.constants import REQUEST_ID_RE
from tests.shared.scenarios import TestScenario, get_smoke_scenarios
diff --git a/tests/onmoto/test_spot_fleet_gaps.py b/tests/providers/aws/moto/test_spot_fleet_gaps.py
similarity index 99%
rename from tests/onmoto/test_spot_fleet_gaps.py
rename to tests/providers/aws/moto/test_spot_fleet_gaps.py
index c5ae8dfcd..91aa75f47 100644
--- a/tests/onmoto/test_spot_fleet_gaps.py
+++ b/tests/providers/aws/moto/test_spot_fleet_gaps.py
@@ -31,7 +31,7 @@
)
from orb.providers.aws.infrastructure.handlers.spot_fleet.handler import SpotFleetHandler
from orb.providers.aws.utilities.aws_operations import AWSOperations
-from tests.onmoto.conftest import (
+from tests.providers.aws.moto.conftest import (
REGION,
_make_config_port,
_make_launch_template_manager,
@@ -387,9 +387,9 @@ def test_check_status_cancelled_fleet_returns_empty(
)
status_request = _make_request(request_id="spot-status-cancel", resource_ids=[fleet_id])
- status = handler.check_hosts_status(status_request)
+ result = handler.check_hosts_status(status_request)
- assert status == []
+ assert result.instances == []
# ---------------------------------------------------------------------------
diff --git a/tests/onmoto/test_template_pipeline.py b/tests/providers/aws/moto/test_template_pipeline.py
similarity index 96%
rename from tests/onmoto/test_template_pipeline.py
rename to tests/providers/aws/moto/test_template_pipeline.py
index a93cd1f05..43af10696 100644
--- a/tests/onmoto/test_template_pipeline.py
+++ b/tests/providers/aws/moto/test_template_pipeline.py
@@ -180,14 +180,20 @@ def test_default_strategy_loads_snake_case_passthrough(tmp_path):
def test_default_strategy_delegates_hf_file_to_hf_strategy(tmp_path):
"""Default strategy detects scheduler_type=hostfactory and delegates via registry when registered."""
+ from orb.infrastructure.scheduler.registration import (
+ register_default_scheduler,
+ register_symphony_hostfactory_scheduler,
+ )
from orb.infrastructure.scheduler.registry import get_scheduler_registry
registry = get_scheduler_registry()
- # Register both types so delegation can resolve the HF strategy class
+ # Use the canonical registration helpers so strategy_class is populated correctly.
+ # register_type is idempotent, so these are safe to call even if a prior test already
+ # registered the types via the DI container boot path.
if not registry.is_registered("hostfactory"):
- registry.register("hostfactory", HostFactorySchedulerStrategy, lambda c: None)
+ register_symphony_hostfactory_scheduler(registry)
if not registry.is_registered("default"):
- registry.register("default", DefaultSchedulerStrategy, lambda c: None)
+ register_default_scheduler(registry)
tpl_file = tmp_path / "aws_templates.json"
_write_hf_file(tpl_file, [_MINIMAL_HF_TEMPLATE])
diff --git a/tests/unit/providers/aws/configuration/__init__.py b/tests/providers/aws/unit/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/configuration/__init__.py
rename to tests/providers/aws/unit/__init__.py
diff --git a/tests/unit/providers/aws/handlers/__init__.py b/tests/providers/aws/unit/cli/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/handlers/__init__.py
rename to tests/providers/aws/unit/cli/__init__.py
diff --git a/tests/unit/providers/aws/cli/test_aws_cli_spec.py b/tests/providers/aws/unit/cli/test_aws_cli_spec.py
similarity index 100%
rename from tests/unit/providers/aws/cli/test_aws_cli_spec.py
rename to tests/providers/aws/unit/cli/test_aws_cli_spec.py
diff --git a/tests/unit/providers/aws/infrastructure/__init__.py b/tests/providers/aws/unit/configuration/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/infrastructure/__init__.py
rename to tests/providers/aws/unit/configuration/__init__.py
diff --git a/tests/unit/providers/aws/configuration/test_aws_batch_sizes_config.py b/tests/providers/aws/unit/configuration/test_aws_batch_sizes_config.py
similarity index 100%
rename from tests/unit/providers/aws/configuration/test_aws_batch_sizes_config.py
rename to tests/providers/aws/unit/configuration/test_aws_batch_sizes_config.py
diff --git a/tests/unit/providers/aws/configuration/test_aws_naming_config.py b/tests/providers/aws/unit/configuration/test_aws_naming_config.py
similarity index 100%
rename from tests/unit/providers/aws/configuration/test_aws_naming_config.py
rename to tests/providers/aws/unit/configuration/test_aws_naming_config.py
diff --git a/tests/unit/providers/aws/configuration/test_cleanup_config.py b/tests/providers/aws/unit/configuration/test_cleanup_config.py
similarity index 100%
rename from tests/unit/providers/aws/configuration/test_cleanup_config.py
rename to tests/providers/aws/unit/configuration/test_cleanup_config.py
diff --git a/tests/unit/providers/aws/infrastructure/handlers/__init__.py b/tests/providers/aws/unit/handlers/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/infrastructure/handlers/__init__.py
rename to tests/providers/aws/unit/handlers/__init__.py
diff --git a/tests/unit/providers/aws/handlers/test_base_handler_circuit_breaker_config.py b/tests/providers/aws/unit/handlers/test_base_handler_circuit_breaker_config.py
similarity index 100%
rename from tests/unit/providers/aws/handlers/test_base_handler_circuit_breaker_config.py
rename to tests/providers/aws/unit/handlers/test_base_handler_circuit_breaker_config.py
diff --git a/tests/unit/providers/aws/handlers/test_fleet_release_regression.py b/tests/providers/aws/unit/handlers/test_fleet_release_regression.py
similarity index 100%
rename from tests/unit/providers/aws/handlers/test_fleet_release_regression.py
rename to tests/providers/aws/unit/handlers/test_fleet_release_regression.py
diff --git a/tests/unit/providers/aws/storage/__init__.py b/tests/providers/aws/unit/infrastructure/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/storage/__init__.py
rename to tests/providers/aws/unit/infrastructure/__init__.py
diff --git a/tests/unit/providers/aws/strategy/__init__.py b/tests/providers/aws/unit/infrastructure/handlers/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/strategy/__init__.py
rename to tests/providers/aws/unit/infrastructure/handlers/__init__.py
diff --git a/tests/unit/providers/aws/utilities/__init__.py b/tests/providers/aws/unit/infrastructure/handlers/shared/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/utilities/__init__.py
rename to tests/providers/aws/unit/infrastructure/handlers/shared/__init__.py
diff --git a/tests/providers/aws/unit/infrastructure/handlers/shared/test_fleet_fulfilment_helper.py b/tests/providers/aws/unit/infrastructure/handlers/shared/test_fleet_fulfilment_helper.py
new file mode 100644
index 000000000..92a4bc538
--- /dev/null
+++ b/tests/providers/aws/unit/infrastructure/handlers/shared/test_fleet_fulfilment_helper.py
@@ -0,0 +1,129 @@
+"""Unit tests for the compute_capacity_based_fulfilment shared helper."""
+
+from __future__ import annotations
+
+import pytest
+
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
+from orb.providers.aws.infrastructure.handlers.shared.fleet_fulfilment import (
+ compute_capacity_based_fulfilment,
+)
+
+
+@pytest.mark.unit
+def test_fully_fulfilled_returns_fulfilled_state():
+ """target_capacity met, all running, no pending/failed → fulfilled."""
+ result = compute_capacity_based_fulfilment(
+ target_capacity=10,
+ fulfilled_capacity=10.0,
+ running_count=10,
+ pending_count=0,
+ failed_count=0,
+ provider_label="Fleet",
+ )
+ assert isinstance(result, ProviderFulfilment)
+ assert result.state == "fulfilled"
+ assert result.running_count == 10
+ assert result.pending_count == 0
+ assert result.failed_count == 0
+ assert result.fulfilled_units == 10
+ assert result.target_units == 10
+
+
+@pytest.mark.unit
+def test_in_progress_when_pending_present():
+ """fulfilled_capacity < target or pending instances → in_progress."""
+ result = compute_capacity_based_fulfilment(
+ target_capacity=10,
+ fulfilled_capacity=4.0,
+ running_count=4,
+ pending_count=6,
+ failed_count=0,
+ provider_label="Fleet",
+ )
+ assert result.state == "in_progress"
+ assert result.running_count == 4
+ assert result.pending_count == 6
+ assert result.failed_count == 0
+
+
+@pytest.mark.unit
+def test_partial_when_failed_present_no_pending():
+ """failed > 0 AND running == 0 AND pending == 0 → failed state (not partial)."""
+ # The implementation: failed>0, running==0, pending==0 → "failed" branch.
+ # When there is a mix (some running, some failed, no pending) → in_progress/else branch.
+ # Test the pure-failed case first.
+ result = compute_capacity_based_fulfilment(
+ target_capacity=5,
+ fulfilled_capacity=0.0,
+ running_count=0,
+ pending_count=0,
+ failed_count=5,
+ provider_label="Fleet",
+ )
+ assert result.state == "failed"
+ assert result.failed_count == 5
+ assert result.running_count == 0
+
+
+@pytest.mark.unit
+def test_in_progress_when_some_running_some_failed_no_pending():
+ """Some running + some failed + no pending → in_progress (else branch)."""
+ result = compute_capacity_based_fulfilment(
+ target_capacity=10,
+ fulfilled_capacity=3.0,
+ running_count=3,
+ pending_count=0,
+ failed_count=2,
+ provider_label="Spot Fleet",
+ )
+ # failed_count > 0 but running_count > 0 → does NOT hit the pure-failed branch
+ assert result.state == "in_progress"
+
+
+@pytest.mark.unit
+def test_provider_label_appears_in_message():
+ """The provider_label value must appear verbatim in the returned message."""
+ result = compute_capacity_based_fulfilment(
+ target_capacity=2,
+ fulfilled_capacity=2.0,
+ running_count=2,
+ pending_count=0,
+ failed_count=0,
+ provider_label="Spot Fleet",
+ )
+ assert "Spot Fleet" in result.message
+
+
+@pytest.mark.unit
+def test_zero_target_capacity_handles_gracefully():
+ """target_capacity=0 with fulfilled_capacity=0 must not raise."""
+ # fulfilled_capacity (0.0) >= target_capacity (0) is True, and pending==0,
+ # failed==0 → fulfilled branch.
+ result = compute_capacity_based_fulfilment(
+ target_capacity=0,
+ fulfilled_capacity=0.0,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ provider_label="Fleet",
+ )
+ assert result.state == "fulfilled"
+ assert result.target_units == 0
+ assert result.fulfilled_units == 0
+
+
+@pytest.mark.unit
+def test_none_target_capacity_uses_fulfilled_as_target():
+ """When target_capacity is None, fulfilled_capacity is used as the target."""
+ result = compute_capacity_based_fulfilment(
+ target_capacity=None,
+ fulfilled_capacity=4.0,
+ running_count=4,
+ pending_count=0,
+ failed_count=0,
+ provider_label="Fleet",
+ )
+ # target_capacity is None → fleet_fully_fulfilled is False → in_progress/else branch.
+ assert result.state == "in_progress"
+ assert result.target_units == 4 # derived from int(fulfilled_capacity)
diff --git a/tests/unit/providers/aws/infrastructure/handlers/test_asg_handler.py b/tests/providers/aws/unit/infrastructure/handlers/test_asg_handler.py
similarity index 56%
rename from tests/unit/providers/aws/infrastructure/handlers/test_asg_handler.py
rename to tests/providers/aws/unit/infrastructure/handlers/test_asg_handler.py
index b4e4af65a..025b23d18 100644
--- a/tests/unit/providers/aws/infrastructure/handlers/test_asg_handler.py
+++ b/tests/providers/aws/unit/infrastructure/handlers/test_asg_handler.py
@@ -5,6 +5,11 @@
from botocore.exceptions import ClientError
+from orb.domain.base.provider_fulfilment import (
+ CheckHostsStatusResult,
+ FulfilmentState,
+ ProviderFulfilment,
+)
from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError
from orb.providers.aws.infrastructure.handlers.asg.handler import ASGHandler
@@ -19,11 +24,12 @@ def _make_handler() -> Any:
return handler
-def _make_request(resource_ids):
+def _make_request(resource_ids, requested_count=2):
request = MagicMock()
request.request_id = "req-asg-123"
request.resource_ids = resource_ids
request.metadata = {}
+ request.requested_count = requested_count
return request
@@ -32,7 +38,7 @@ def _make_client_error(code="InternalError"):
def _formatted_instances(instance_ids, resource_id="asg-test"):
- """Return already-formatted instance dicts (as _get_asg_instances returns them)."""
+ """Return already-formatted instance dicts."""
return [
{
"instance_id": iid,
@@ -51,157 +57,176 @@ def _formatted_instances(instance_ids, resource_id="asg-test"):
]
+def _asg_result(instance_ids, resource_id="asg-test", state: FulfilmentState = "fulfilled"):
+ """Build a CheckHostsStatusResult for mocking _get_asg_status."""
+ return CheckHostsStatusResult(
+ instances=_formatted_instances(instance_ids, resource_id),
+ fulfilment=ProviderFulfilment(state=state, message="test"),
+ )
+
+
class TestASGHandlerCheckHostsStatus:
def test_check_hosts_status_all_inservice(self):
- """All InService instances → returns all."""
+ """All InService instances → CheckHostsStatusResult with all instances and fulfilled state."""
handler = _make_handler()
request = _make_request(["asg-111"])
instance_ids = ["i-asg1", "i-asg2"]
with patch.object(
handler,
- "_get_asg_instances",
- return_value=_formatted_instances(instance_ids, "asg-111"),
+ "_get_asg_status",
+ return_value=_asg_result(instance_ids, "asg-111", state="fulfilled"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 2
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert len(result.instances) == 2
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(instance_ids)
+ assert result.fulfilment.state == "fulfilled"
def test_check_hosts_status_mixed_lifecycle(self):
- """_get_asg_instances extracts all instance IDs from ASG regardless of lifecycle state."""
+ """ASG with mixed lifecycle states — handler reports what _get_asg_status returns."""
handler = _make_handler()
request = _make_request(["asg-222"])
- # ASG returns InService + Terminating instances — handler returns all from _get_asg_instances
all_ids = ["i-inservice1", "i-inservice2", "i-terminating1"]
with patch.object(
- handler, "_get_asg_instances", return_value=_formatted_instances(all_ids, "asg-222")
+ handler,
+ "_get_asg_status",
+ return_value=_asg_result(all_ids, "asg-222", state="in_progress"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 3
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert len(result.instances) == 3
def test_check_hosts_status_asg_not_found(self):
- """_get_asg_instances returns [] when ASG not found → result is []."""
+ """_get_asg_status raises → exception logged and skipped; result has empty instances and in_progress."""
handler = _make_handler()
request = _make_request(["asg-missing"])
- with patch.object(handler, "_get_asg_instances", return_value=[]):
+ with patch.object(
+ handler, "_get_asg_status", side_effect=AWSInfrastructureError("ASG not found")
+ ):
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.instances == []
+ assert result.fulfilment.state == "in_progress"
def test_check_hosts_status_aws_error(self):
- """Exception inside per-ASG loop → logged and skipped; result is []."""
+ """Exception inside per-ASG loop → logged and skipped; empty instances, in_progress."""
handler = _make_handler()
request = _make_request(["asg-err"])
with patch.object(
- handler, "_get_asg_instances", side_effect=AWSInfrastructureError("AWS error")
+ handler, "_get_asg_status", side_effect=AWSInfrastructureError("AWS error")
):
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.instances == []
+ assert result.fulfilment.state == "in_progress"
def test_check_hosts_status_no_resource_ids(self):
- """Empty resource_ids → returns [] immediately without calling AWS."""
+ """Empty resource_ids → CheckHostsStatusResult with empty instances and in_progress, no AWS calls."""
handler = _make_handler()
request = _make_request([])
- with patch.object(handler, "_get_asg_instances") as mock_get:
+ with patch.object(handler, "_get_asg_status") as mock_get:
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.instances == []
+ assert result.fulfilment.state == "in_progress"
mock_get.assert_not_called()
def test_check_hosts_status_returns_correct_count(self):
- """Verify count matches instances in ASG."""
+ """Verify instance count in result.instances matches instances returned by _get_asg_status."""
handler = _make_handler()
request = _make_request(["asg-cnt"])
instance_ids = ["i-cnt1", "i-cnt2", "i-cnt3"]
with patch.object(
handler,
- "_get_asg_instances",
- return_value=_formatted_instances(instance_ids, "asg-cnt"),
+ "_get_asg_status",
+ return_value=_asg_result(instance_ids, "asg-cnt", state="fulfilled"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 3
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert len(result.instances) == 3
+ assert result.fulfilment.state == "fulfilled"
def test_check_hosts_status_preserves_instance_ids(self):
- """Instance IDs in result match input."""
+ """Instance IDs in result.instances match input."""
handler = _make_handler()
request = _make_request(["asg-ids"])
instance_ids = ["i-asg-preserve1", "i-asg-preserve2"]
with patch.object(
handler,
- "_get_asg_instances",
- return_value=_formatted_instances(instance_ids, "asg-ids"),
+ "_get_asg_status",
+ return_value=_asg_result(instance_ids, "asg-ids", state="fulfilled"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(instance_ids)
+ assert result.fulfilment.state == "fulfilled"
def test_check_hosts_status_multiple_asgs(self):
- """Multiple ASG names in resource_ids → aggregates results from all."""
+ """Multiple ASG names in resource_ids → aggregates instances from all ASGs."""
handler = _make_handler()
request = _make_request(["asg-A", "asg-B"])
ids_a = ["i-a1", "i-a2"]
ids_b = ["i-b1"]
- def get_asg_instances_side_effect(asg_name, **kwargs):
+ def get_asg_status_side_effect(asg_name, **kwargs):
if asg_name == "asg-A":
- return _formatted_instances(ids_a, "asg-A")
- return _formatted_instances(ids_b, "asg-B")
+ return _asg_result(ids_a, "asg-A", state="fulfilled")
+ return _asg_result(ids_b, "asg-B", state="fulfilled")
- with patch.object(handler, "_get_asg_instances", side_effect=get_asg_instances_side_effect):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ with patch.object(handler, "_get_asg_status", side_effect=get_asg_status_side_effect):
+ result = handler.check_hosts_status(request)
- assert len(result) == 3
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert len(result.instances) == 3
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == {"i-a1", "i-a2", "i-b1"}
+ assert result.fulfilment.state == "fulfilled"
def test_check_hosts_status_state_filtering_is_strict(self):
- """Only instances returned by _get_asg_instances appear in result."""
+ """Only instances returned by _get_asg_status appear in result.instances."""
handler = _make_handler()
request = _make_request(["asg-strict"])
- # Only one instance is active
active_ids = ["i-strict-active"]
with patch.object(
handler,
- "_get_asg_instances",
- return_value=_formatted_instances(active_ids, "asg-strict"),
+ "_get_asg_status",
+ return_value=_asg_result(active_ids, "asg-strict", state="fulfilled"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 1
- assert result[0]["instance_id"] == "i-strict-active"
+ assert isinstance(result, CheckHostsStatusResult)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert len(result.instances) == 1
+ assert result.instances[0]["instance_id"] == "i-strict-active"
+ assert result.fulfilment.state == "fulfilled"
+ assert result.fulfilment.target_units is None
class TestASGHandlerNameTag:
diff --git a/tests/unit/providers/aws/infrastructure/handlers/test_base_handler_fallback.py b/tests/providers/aws/unit/infrastructure/handlers/test_base_handler_fallback.py
similarity index 100%
rename from tests/unit/providers/aws/infrastructure/handlers/test_base_handler_fallback.py
rename to tests/providers/aws/unit/infrastructure/handlers/test_base_handler_fallback.py
diff --git a/tests/unit/providers/aws/infrastructure/handlers/test_cleanup.py b/tests/providers/aws/unit/infrastructure/handlers/test_cleanup.py
similarity index 96%
rename from tests/unit/providers/aws/infrastructure/handlers/test_cleanup.py
rename to tests/providers/aws/unit/infrastructure/handlers/test_cleanup.py
index fb5222e7f..6a8a587ed 100644
--- a/tests/unit/providers/aws/infrastructure/handlers/test_cleanup.py
+++ b/tests/providers/aws/unit/infrastructure/handlers/test_cleanup.py
@@ -310,6 +310,11 @@ def test_partial_return_does_not_trigger_lt_cleanup(self):
with (
patch.object(handler._fleet_release_manager, "_delete_fleet") as mock_delete_fleet,
patch.object(handler, "_delete_orb_launch_template") as mock_delete_lt,
+ patch.object(
+ handler._fleet_release_manager,
+ "_fleet_has_no_remaining_instances",
+ return_value=False,
+ ),
):
handler._release_hosts_for_single_ec2_fleet(
"fleet-123",
@@ -390,7 +395,14 @@ def test_partial_return_does_not_trigger_lt_cleanup(self):
config_port = _make_config_port()
handler = _make_spot_fleet_handler(config_port=config_port)
- with patch.object(handler, "_delete_orb_launch_template") as mock_delete_lt:
+ with (
+ patch.object(handler, "_delete_orb_launch_template") as mock_delete_lt,
+ patch.object(
+ handler._release_manager,
+ "_fleet_has_no_remaining_instances",
+ return_value=False,
+ ),
+ ):
handler._release_hosts_for_single_spot_fleet(
"sfr-123",
["i-1"],
@@ -552,13 +564,17 @@ def test_request_type_deletes_fleet_with_terminate_false_and_calls_cleanup(self)
)
cleanup_fn.assert_called_once_with("ec2_fleet", "req-ec2-abc")
- def test_instant_type_no_fleet_delete_but_calls_cleanup(self):
+ def test_instant_type_calls_delete_fleets_and_cleanup(self):
+ # Instant fleets are NOT guaranteed to be auto-deleted by AWS; ORB must
+ # issue an explicit delete_fleets so the fleet is cleaned up promptly.
cleanup_fn = MagicMock()
mgr, aws_client, _ = _make_ec2_fleet_release_manager(cleanup_fn)
with patch.object(mgr, "_delete_fleet") as mock_delete:
mgr.release("fleet-3", ["i-1"], self._fleet_details("instant", 1))
mock_delete.assert_not_called()
- aws_client.ec2_client.delete_fleets.assert_not_called()
+ aws_client.ec2_client.delete_fleets.assert_called_once_with(
+ FleetIds=["fleet-3"], TerminateInstances=True
+ )
cleanup_fn.assert_called_once_with("ec2_fleet", "req-ec2-abc")
def test_request_type_missing_request_id_tag_skips_cleanup_gracefully(self):
@@ -806,16 +822,16 @@ def test_empty_asg_details_logs_warning(self):
mgr.release_instances("asg-no-details", ["i-1"], {})
cast(MagicMock, mgr._logger.warning).assert_called()
- def test_empty_asg_details_does_not_call_detach(self):
+ def test_empty_asg_details_does_not_call_terminate_in_asg(self):
mgr, aws_client, _cleanup_fn = _make_asg_capacity_manager()
aws_client.autoscaling_client.describe_auto_scaling_groups.return_value = (
self._empty_describe_response()
)
mgr.release_instances("asg-no-details", ["i-1"], {})
- aws_client.autoscaling_client.detach_instances.assert_not_called()
+ aws_client.autoscaling_client.terminate_instance_in_auto_scaling_group.assert_not_called()
def test_empty_asg_details_retry_succeeds_continues_normal_path(self):
- """When the retry describe returns a valid ASG, normal detach+terminate path runs."""
+ """When the retry describe returns a valid ASG, terminate_instance_in_auto_scaling_group is called."""
cleanup_fn = MagicMock()
mgr, aws_client, _ = _make_asg_capacity_manager(cleanup_fn)
aws_client.autoscaling_client.describe_auto_scaling_groups.return_value = {
@@ -824,8 +840,9 @@ def test_empty_asg_details_retry_succeeds_continues_normal_path(self):
delete_fn = MagicMock()
mgr.set_delete_asg_fn(delete_fn)
mgr.release_instances("asg-retry-ok", ["i-1"], {})
- aws_client.autoscaling_client.detach_instances.assert_called_once()
- cast(MagicMock, mgr._aws_ops.terminate_instances_with_fallback).assert_called_once()
+ aws_client.autoscaling_client.terminate_instance_in_auto_scaling_group.assert_called_once_with(
+ InstanceId="i-1", ShouldDecrementDesiredCapacity=True
+ )
def test_valid_asg_details_at_zero_capacity_calls_delete_and_cleanup(self):
cleanup_fn = MagicMock()
diff --git a/tests/providers/aws/unit/infrastructure/handlers/test_ec2_fleet_handler.py b/tests/providers/aws/unit/infrastructure/handlers/test_ec2_fleet_handler.py
new file mode 100644
index 000000000..91370ce5c
--- /dev/null
+++ b/tests/providers/aws/unit/infrastructure/handlers/test_ec2_fleet_handler.py
@@ -0,0 +1,339 @@
+"""Unit tests for EC2FleetHandler.check_hosts_status and ProviderFulfilment."""
+
+from unittest.mock import MagicMock, patch
+
+import pytest
+from botocore.exceptions import ClientError
+
+from orb.domain.base.provider_fulfilment import (
+ CheckHostsStatusResult,
+ FulfilmentState,
+ ProviderFulfilment,
+)
+from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError
+from orb.providers.aws.infrastructure.handlers.ec2_fleet.handler import EC2FleetHandler
+
+
+def _make_handler():
+ aws_client = MagicMock()
+ logger = MagicMock()
+ aws_ops = MagicMock()
+ launch_template_manager = MagicMock()
+ handler = EC2FleetHandler(aws_client, logger, aws_ops, launch_template_manager)
+ handler._machine_adapter = None
+ return handler
+
+
+def _make_request(resource_ids, metadata=None, requested_count=2):
+ request = MagicMock()
+ request.request_id = "req-ec2fleet-123"
+ request.resource_ids = resource_ids
+ request.metadata = metadata or {}
+ request.requested_count = requested_count
+ return request
+
+
+def _make_client_error(code="InternalError"):
+ return ClientError({"Error": {"Code": code, "Message": "boom"}}, "DescribeFleets")
+
+
+def _inst(iid, resource_id="fleet-test", status="running"):
+ return {
+ "instance_id": iid,
+ "resource_id": resource_id,
+ "status": status,
+ "private_ip": "10.0.0.1",
+ "public_ip": None,
+ "launch_time": None,
+ "instance_type": "t3.medium",
+ "image_id": "ami-123",
+ "subnet_id": None,
+ "security_group_ids": [],
+ "vpc_id": None,
+ }
+
+
+def _formatted_instances(instance_ids, resource_id="fleet-test"):
+ return [_inst(iid, resource_id) for iid in instance_ids]
+
+
+def _fleet_result(instance_ids, resource_id="fleet-test", state: FulfilmentState = "fulfilled"):
+ """Build a CheckHostsStatusResult for mocking _check_single_fleet_status."""
+ return CheckHostsStatusResult(
+ instances=_formatted_instances(instance_ids, resource_id),
+ fulfilment=ProviderFulfilment(state=state, message="test"),
+ )
+
+
+class TestEC2FleetHandlerCheckHostsStatus:
+ def test_check_hosts_status_returns_check_hosts_status_result(self):
+ """check_hosts_status returns CheckHostsStatusResult (not a plain list)."""
+ handler = _make_handler()
+ request = _make_request(["fleet-111"], metadata={"fleet_type": "maintain"})
+ with patch.object(
+ handler, "_check_single_fleet_status", return_value=_fleet_result(["i-aaa"])
+ ):
+ result = handler.check_hosts_status(request)
+ assert isinstance(result, CheckHostsStatusResult)
+
+ def test_check_hosts_status_all_running(self):
+ """All instances running → instances present and fulfilled."""
+ handler = _make_handler()
+ request = _make_request(["fleet-111"], metadata={"fleet_type": "maintain"})
+ instance_ids = ["i-aaa", "i-bbb"]
+ with patch.object(
+ handler,
+ "_check_single_fleet_status",
+ return_value=_fleet_result(instance_ids, "fleet-111"),
+ ):
+ result = handler.check_hosts_status(request)
+
+ assert len(result.instances) == 2
+ returned_ids = {r["instance_id"] for r in result.instances}
+ assert returned_ids == set(instance_ids)
+
+ def test_check_hosts_status_mixed_states(self):
+ """Active instances returned as reported."""
+ handler = _make_handler()
+ request = _make_request(["fleet-222"], metadata={"fleet_type": "maintain"})
+ with patch.object(
+ handler,
+ "_check_single_fleet_status",
+ return_value=_fleet_result(["i-running1"], "fleet-222"),
+ ):
+ result = handler.check_hosts_status(request)
+
+ assert len(result.instances) == 1
+ assert result.instances[0]["instance_id"] == "i-running1"
+
+ def test_check_hosts_status_fleet_not_found(self):
+ """_check_single_fleet_status raises → exception logged; result has empty instances."""
+ handler = _make_handler()
+ request = _make_request(["fleet-missing"], metadata={"fleet_type": "maintain"})
+ with patch.object(
+ handler,
+ "_check_single_fleet_status",
+ side_effect=AWSInfrastructureError("Fleet not found"),
+ ):
+ result = handler.check_hosts_status(request)
+
+ assert result.instances == []
+ assert result.fulfilment.state == "in_progress"
+
+ def test_check_hosts_status_multiple_resource_ids(self):
+ """Request has 2 fleet IDs → checks both, aggregates instances."""
+ handler = _make_handler()
+ request = _make_request(["fleet-A", "fleet-B"], metadata={"fleet_type": "maintain"})
+ ids_a = ["i-a1", "i-a2"]
+ ids_b = ["i-b1"]
+
+ def single_fleet_side_effect(fleet_id, req):
+ if fleet_id == "fleet-A":
+ return _fleet_result(ids_a, "fleet-A")
+ return _fleet_result(ids_b, "fleet-B")
+
+ with patch.object(
+ handler, "_check_single_fleet_status", side_effect=single_fleet_side_effect
+ ):
+ result = handler.check_hosts_status(request)
+
+ assert len(result.instances) == 3
+ returned_ids = {r["instance_id"] for r in result.instances}
+ assert returned_ids == {"i-a1", "i-a2", "i-b1"}
+
+ def test_check_hosts_status_aws_error(self):
+ """ClientError inside per-fleet loop → logged and skipped; empty instances."""
+ handler = _make_handler()
+ request = _make_request(["fleet-err"], metadata={"fleet_type": "maintain"})
+ with patch.object(
+ handler, "_check_single_fleet_status", side_effect=_make_client_error("InternalError")
+ ):
+ result = handler.check_hosts_status(request)
+
+ assert result.instances == []
+
+ def test_check_hosts_status_no_resource_ids(self):
+ """Empty resource_ids → raises AWSInfrastructureError immediately."""
+ handler = _make_handler()
+ request = _make_request([])
+ with pytest.raises(AWSInfrastructureError):
+ handler.check_hosts_status(request)
+
+ def test_check_hosts_status_returns_correct_count(self):
+ """Verify instance count in result matches instances returned."""
+ handler = _make_handler()
+ request = _make_request(["fleet-cnt"], metadata={"fleet_type": "maintain"})
+ with patch.object(
+ handler, "_check_single_fleet_status", return_value=_fleet_result(["i-1", "i-2", "i-3"])
+ ):
+ result = handler.check_hosts_status(request)
+
+ assert len(result.instances) == 3
+
+ def test_check_hosts_status_instant_fleet_no_active_instances(self):
+ """Instant fleet with no instances → empty instances, in_progress."""
+ handler = _make_handler()
+ request = _make_request(
+ ["fleet-instant"], metadata={"fleet_type": "instant", "instance_ids": []}
+ )
+ empty_result = CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(state="in_progress", message="waiting"),
+ )
+ with patch.object(handler, "_check_single_fleet_status", return_value=empty_result):
+ result = handler.check_hosts_status(request)
+
+ assert result.instances == []
+ assert result.fulfilment.state == "in_progress"
+
+
+class TestEC2FleetFulfilment:
+ """Unit tests for EC2Fleet fulfilment computation."""
+
+ def _handler(self):
+ return _make_handler()
+
+ def _inst_dict(self, status):
+ return {"instance_id": "i-x", "status": status}
+
+ def test_maintain_fleet_fulfilled_when_capacity_met_and_no_pending(self):
+ """Maintain: FulfilledCapacity >= TargetCapacity AND pending==0 → fulfilled."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ h = self._handler()
+ instances = [self._inst_dict("running"), self._inst_dict("running")]
+ f = h._compute_ec2fleet_fulfilment(
+ fleet_type=AWSFleetType.MAINTAIN,
+ instances=instances,
+ target_capacity=4,
+ fulfilled_capacity=4.0,
+ requested_count=4,
+ )
+ assert f.state == "fulfilled"
+ assert f.running_count == 2
+ assert f.fulfilled_units == 4
+
+ def test_maintain_fleet_in_progress_when_pending_exists(self):
+ """Maintain: capacity met but pending instances → in_progress."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ h = self._handler()
+ instances = [self._inst_dict("running"), self._inst_dict("pending")]
+ f = h._compute_ec2fleet_fulfilment(
+ fleet_type=AWSFleetType.MAINTAIN,
+ instances=instances,
+ target_capacity=4,
+ fulfilled_capacity=4.0,
+ requested_count=4,
+ )
+ assert f.state == "in_progress"
+
+ def test_maintain_fleet_in_progress_when_capacity_below_target(self):
+ """Maintain: FulfilledCapacity < TargetCapacity → in_progress."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ h = self._handler()
+ instances = [self._inst_dict("running")]
+ f = h._compute_ec2fleet_fulfilment(
+ fleet_type=AWSFleetType.MAINTAIN,
+ instances=instances,
+ target_capacity=4,
+ fulfilled_capacity=2.0,
+ requested_count=4,
+ )
+ assert f.state == "in_progress"
+ assert f.fulfilled_units == 2
+
+ def test_instant_fleet_fulfilled_when_count_meets_requested(self):
+ """Instant: running_count >= requested_count → fulfilled."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ h = self._handler()
+ instances = [self._inst_dict("running"), self._inst_dict("running")]
+ f = h._compute_ec2fleet_fulfilment(
+ fleet_type=AWSFleetType.INSTANT,
+ instances=instances,
+ target_capacity=2,
+ fulfilled_capacity=2.0,
+ requested_count=2,
+ )
+ assert f.state == "fulfilled"
+
+ def test_instant_fleet_partial_when_fewer_running_no_pending(self):
+ """Instant: running < requested, no pending → partial (final)."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ h = self._handler()
+ instances = [self._inst_dict("running")]
+ f = h._compute_ec2fleet_fulfilment(
+ fleet_type=AWSFleetType.INSTANT,
+ instances=instances,
+ target_capacity=4,
+ fulfilled_capacity=1.0,
+ requested_count=4,
+ )
+ assert f.state == "partial"
+
+ def test_instant_fleet_in_progress_when_no_instances_yet(self):
+ """Instant: no instances yet → in_progress."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ h = self._handler()
+ f = h._compute_ec2fleet_fulfilment(
+ fleet_type=AWSFleetType.INSTANT,
+ instances=[],
+ target_capacity=2,
+ fulfilled_capacity=0.0,
+ requested_count=2,
+ )
+ assert f.state == "in_progress"
+
+
+class TestEC2FleetHandlerNameTag:
+ def test_fleet_config_instance_tag_uses_config_prefix(self):
+ """Instance Name tag in EC2Fleet config uses config_port prefix (instant fleet only)."""
+ from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
+
+ aws_client = MagicMock()
+ logger = MagicMock()
+ aws_ops = MagicMock()
+ launch_template_manager = MagicMock()
+ config_port = MagicMock()
+ config_port.get_resource_prefix.side_effect = lambda rt: (
+ "pfx-" if rt == "fleet" else "inst-"
+ )
+
+ handler = EC2FleetHandler(
+ aws_client, logger, aws_ops, launch_template_manager, config_port=config_port
+ )
+
+ template = MagicMock()
+ template.fleet_type = AWSFleetType.INSTANT
+ template.tags = {}
+ template.price_type = "ondemand"
+ template.allocation_strategy = None
+ template.max_price = None
+ template.machine_types = {"m5.large": 1}
+ template.subnet_ids = ["subnet-abc"]
+ template.template_id = "tmpl-ec2"
+ template.percent_on_demand = None
+ template.context = None
+
+ request = MagicMock()
+ request.request_id = "req-ec2-001"
+ request.requested_count = 2
+
+ fleet_config = handler._create_fleet_config(template, request, "lt-xyz", "$Default")
+
+ instance_ts = next(
+ (
+ ts
+ for ts in fleet_config.get("TagSpecifications", [])
+ if ts["ResourceType"] == "instance"
+ ),
+ None,
+ )
+ assert instance_ts is not None, "No instance TagSpecification found (instant fleet)"
+ name_tag = next((t for t in instance_ts["Tags"] if t["Key"] == "Name"), None)
+ assert name_tag is not None, "No Name tag in instance TagSpecification"
+ assert "req-ec2-001" in name_tag["Value"]
diff --git a/tests/providers/aws/unit/infrastructure/handlers/test_fleet_release_weighted_capacity.py b/tests/providers/aws/unit/infrastructure/handlers/test_fleet_release_weighted_capacity.py
new file mode 100644
index 000000000..56510fd6a
--- /dev/null
+++ b/tests/providers/aws/unit/infrastructure/handlers/test_fleet_release_weighted_capacity.py
@@ -0,0 +1,514 @@
+"""Unit tests for weighted-capacity arithmetic in EC2Fleet and SpotFleet release managers.
+
+Verifies that:
+- compute_fleet_release_decision uses weighted_capacity_to_return (not instance count).
+- EC2FleetReleaseManager._sum_weighted_capacity sums WeightedCapacity per InstanceType.
+- SpotFleetReleaseManager._sum_weighted_capacity uses per-instance WeightedCapacity from
+ describe_spot_fleet_instances, with a fallback to the fleet spec weight-by-type map.
+- The release() path passes the weighted sum to modify_fleet / modify_spot_fleet_request.
+"""
+
+from typing import Any
+from unittest.mock import MagicMock
+
+from orb.providers.aws.infrastructure.handlers.ec2_fleet.release_manager import (
+ EC2FleetReleaseManager,
+)
+from orb.providers.aws.infrastructure.handlers.fleet_release_policy import (
+ compute_fleet_release_decision,
+)
+from orb.providers.aws.infrastructure.handlers.spot_fleet.release_manager import (
+ SpotFleetReleaseManager,
+)
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _make_ec2_release_manager(ec2_client_mock: Any = None) -> EC2FleetReleaseManager:
+ aws_client = MagicMock()
+ if ec2_client_mock is not None:
+ aws_client.ec2_client = ec2_client_mock
+ logger = MagicMock()
+ aws_ops = MagicMock()
+ aws_ops.terminate_instances_with_fallback = MagicMock()
+
+ def identity_retry(fn, operation_type="standard", **kwargs):
+ if callable(fn) and not kwargs:
+ return fn()
+ return fn(**kwargs)
+
+ def identity_paginate(method, key, **kwargs):
+ return method(**kwargs).get(key, [])
+
+ def identity_collect(method, key, **kwargs):
+ return method(**kwargs).get(key, [])
+
+ return EC2FleetReleaseManager(
+ aws_client=aws_client,
+ aws_ops=aws_ops,
+ request_adapter=None,
+ config_port=None,
+ logger=logger,
+ retry_fn=identity_retry,
+ paginate_fn=identity_paginate,
+ collect_with_next_token_fn=identity_collect,
+ cleanup_on_zero_capacity_fn=MagicMock(),
+ )
+
+
+def _make_spot_release_manager(ec2_client_mock: Any = None) -> SpotFleetReleaseManager:
+ aws_client = MagicMock()
+ if ec2_client_mock is not None:
+ aws_client.ec2_client = ec2_client_mock
+ logger = MagicMock()
+ aws_ops = MagicMock()
+ aws_ops.terminate_instances_with_fallback = MagicMock()
+
+ def identity_retry(fn, operation_type="standard", **kwargs):
+ if callable(fn) and not kwargs:
+ return fn()
+ return fn(**kwargs)
+
+ mgr = SpotFleetReleaseManager(
+ aws_client=aws_client,
+ aws_ops=aws_ops,
+ request_adapter=None,
+ cleanup_on_zero_capacity_fn=MagicMock(),
+ logger=logger,
+ retry_fn=identity_retry,
+ )
+ return mgr
+
+
+def _fleet_details(fleet_type: str, total_capacity: int, overrides: list[dict]) -> dict:
+ """Build a minimal DescribeFleets entry with LaunchTemplateConfigs overrides."""
+ return {
+ "Type": fleet_type,
+ "TargetCapacitySpecification": {"TotalTargetCapacity": total_capacity},
+ "LaunchTemplateConfigs": [{"Overrides": overrides}],
+ "Tags": [{"Key": "orb:request-id", "Value": "req-test-001"}],
+ }
+
+
+def _spot_fleet_config(fleet_type: str, target_capacity: int, launch_specs: list[dict]) -> dict:
+ """Build a minimal SpotFleetRequestConfig with LaunchSpecifications."""
+ return {
+ "Type": fleet_type,
+ "TargetCapacity": target_capacity,
+ "OnDemandTargetCapacity": 0,
+ "LaunchSpecifications": launch_specs,
+ }
+
+
+def _active_instances_response(instances: list[dict]) -> dict:
+ """Wrap a list of instance dicts into a describe_fleet_instances response."""
+ return {"ActiveInstances": instances}
+
+
+# ---------------------------------------------------------------------------
+# fleet_release_policy unit tests
+# ---------------------------------------------------------------------------
+
+
+class TestComputeFleetReleaseDecision:
+ def test_maintain_partial_return_requires_capacity_reduction(self):
+ decision = compute_fleet_release_decision(
+ fleet_type="maintain",
+ current_capacity=10,
+ weighted_capacity_to_return=4,
+ )
+ assert decision.requires_capacity_reduction is True
+ assert decision.has_fleet_record is True
+ assert decision.is_full_return is False
+
+ def test_maintain_full_return_is_full(self):
+ decision = compute_fleet_release_decision(
+ fleet_type="maintain",
+ current_capacity=4,
+ weighted_capacity_to_return=4,
+ )
+ assert decision.requires_capacity_reduction is True
+ assert decision.is_full_return is True
+
+ def test_maintain_weighted_return_exceeds_capacity_clamps_to_full(self):
+ """If weighted sum > current (race / stale data), still produces is_full_return=True."""
+ decision = compute_fleet_release_decision(
+ fleet_type="maintain",
+ current_capacity=3,
+ weighted_capacity_to_return=8,
+ )
+ assert decision.is_full_return is True
+
+ def test_request_fleet_no_capacity_reduction_required(self):
+ decision = compute_fleet_release_decision(
+ fleet_type="request",
+ current_capacity=5,
+ weighted_capacity_to_return=2,
+ )
+ assert decision.requires_capacity_reduction is False
+ assert decision.has_fleet_record is True
+ assert decision.is_full_return is False
+
+ def test_instant_fleet_no_fleet_record(self):
+ decision = compute_fleet_release_decision(
+ fleet_type="instant",
+ current_capacity=2,
+ weighted_capacity_to_return=2,
+ )
+ assert decision.requires_capacity_reduction is False
+ assert decision.has_fleet_record is False
+ assert decision.is_full_return is True
+
+ def test_weighted_capacity_used_not_instance_count(self):
+ """Two instances with WeightedCapacity=4 each: subtract 8, not 2."""
+ decision = compute_fleet_release_decision(
+ fleet_type="maintain",
+ current_capacity=8,
+ weighted_capacity_to_return=8,
+ )
+ assert decision.is_full_return is True
+
+ # With old (instance-count) arithmetic this would be False (8 - 2 == 6 != 0).
+ decision_old_style = compute_fleet_release_decision(
+ fleet_type="maintain",
+ current_capacity=8,
+ weighted_capacity_to_return=2,
+ )
+ assert decision_old_style.is_full_return is False
+
+
+# ---------------------------------------------------------------------------
+# EC2FleetReleaseManager._sum_weighted_capacity
+# ---------------------------------------------------------------------------
+
+
+class TestEC2FleetSumWeightedCapacity:
+ def _make_manager(self, active_instances: list[dict]) -> EC2FleetReleaseManager:
+ ec2 = MagicMock()
+ ec2.describe_fleet_instances.return_value = {"ActiveInstances": active_instances}
+ return _make_ec2_release_manager(ec2_client_mock=ec2)
+
+ def test_uniform_weights_returns_sum(self):
+ """Two m5.large instances each with WeightedCapacity=2 → returns 4."""
+ active = [
+ {"InstanceId": "i-aaa", "InstanceType": "m5.large"},
+ {"InstanceId": "i-bbb", "InstanceType": "m5.large"},
+ ]
+ overrides = [{"InstanceType": "m5.large", "WeightedCapacity": 2}]
+ mgr = self._make_manager(active)
+ details = _fleet_details("maintain", 4, overrides)
+ result = mgr._sum_weighted_capacity("fleet-111", details, ["i-aaa", "i-bbb"])
+ assert result == 4
+
+ def test_mixed_weights_sums_correctly(self):
+ """One c5.xlarge (weight 4) + one m5.large (weight 2) → 6."""
+ active = [
+ {"InstanceId": "i-aaa", "InstanceType": "c5.xlarge"},
+ {"InstanceId": "i-bbb", "InstanceType": "m5.large"},
+ ]
+ overrides = [
+ {"InstanceType": "c5.xlarge", "WeightedCapacity": 4},
+ {"InstanceType": "m5.large", "WeightedCapacity": 2},
+ ]
+ mgr = self._make_manager(active)
+ details = _fleet_details("maintain", 6, overrides)
+ result = mgr._sum_weighted_capacity("fleet-222", details, ["i-aaa", "i-bbb"])
+ assert result == 6
+
+ def test_instance_not_in_active_defaults_to_1(self):
+ """Instance absent from ActiveInstances (already terminated) defaults to weight 1."""
+ active: list[dict] = [] # already gone
+ overrides = [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ mgr = self._make_manager(active)
+ details = _fleet_details("maintain", 4, overrides)
+ result = mgr._sum_weighted_capacity("fleet-333", details, ["i-gone"])
+ assert result == 1
+
+ def test_no_overrides_defaults_to_instance_count(self):
+ """Fleet with no WeightedCapacity overrides → sum equals instance count."""
+ active = [
+ {"InstanceId": "i-aaa", "InstanceType": "t3.medium"},
+ {"InstanceId": "i-bbb", "InstanceType": "t3.medium"},
+ ]
+ mgr = self._make_manager(active)
+ details = _fleet_details("maintain", 2, overrides=[])
+ result = mgr._sum_weighted_capacity("fleet-444", details, ["i-aaa", "i-bbb"])
+ assert result == 2
+
+ def test_describe_fleet_instances_error_defaults_all_to_1(self):
+ """If describe_fleet_instances raises, all instances default to weight 1."""
+ ec2 = MagicMock()
+ ec2.describe_fleet_instances.side_effect = Exception("throttled")
+ mgr = _make_ec2_release_manager(ec2_client_mock=ec2)
+ overrides = [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ details = _fleet_details("maintain", 8, overrides)
+ result = mgr._sum_weighted_capacity("fleet-555", details, ["i-aaa", "i-bbb"])
+ assert result == 2 # 2 instances × default weight 1
+
+ def test_minimum_result_is_1(self):
+ """Even with an empty instance_ids list the result is at least 1."""
+ ec2 = MagicMock()
+ ec2.describe_fleet_instances.return_value = {"ActiveInstances": []}
+ mgr = _make_ec2_release_manager(ec2_client_mock=ec2)
+ details = _fleet_details("maintain", 4, [])
+ # Pass a single instance that defaults to weight 1 (empty active list).
+ result = mgr._sum_weighted_capacity("fleet-666", details, ["i-only"])
+ assert result >= 1
+
+
+# ---------------------------------------------------------------------------
+# EC2FleetReleaseManager.release() — weighted path end-to-end
+# ---------------------------------------------------------------------------
+
+
+class TestEC2FleetReleaseWeighted:
+ def _setup(self, active_instances: list[dict], total_capacity: int, overrides: list[dict]):
+ ec2 = MagicMock()
+ ec2.describe_fleet_instances.return_value = {"ActiveInstances": active_instances}
+ # After our instances are terminated, remaining fleet is also empty → delete
+ ec2.describe_fleet_instances.side_effect = [
+ {"ActiveInstances": active_instances}, # first call: _sum_weighted_capacity
+ {"ActiveInstances": []}, # second call: _fleet_has_no_remaining_instances
+ ]
+ ec2.modify_fleet.return_value = {}
+ ec2.delete_fleets.return_value = {
+ "SuccessfulFleetDeletions": [],
+ "UnsuccessfulFleetDeletions": [],
+ }
+ mgr = _make_ec2_release_manager(ec2_client_mock=ec2)
+ fleet_details = _fleet_details("maintain", total_capacity, overrides)
+ fleet_details["Tags"] = [{"Key": "orb:request-id", "Value": "req-weighted-001"}]
+ return mgr, ec2, fleet_details
+
+ def test_modify_fleet_called_with_weighted_new_capacity(self):
+ """modify_fleet receives current_capacity - sum(weights), not current_capacity - count."""
+ active = [
+ {"InstanceId": "i-aaa", "InstanceType": "c5.xlarge"},
+ {"InstanceId": "i-bbb", "InstanceType": "c5.xlarge"},
+ ]
+ # Each c5.xlarge has WeightedCapacity=4; returning 2 → subtract 8.
+ overrides = [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ mgr, ec2, fleet_details = self._setup(active, total_capacity=8, overrides=overrides)
+
+ mgr.release(
+ fleet_id="fleet-weighted",
+ instance_ids=["i-aaa", "i-bbb"],
+ fleet_details=fleet_details,
+ )
+
+ ec2.modify_fleet.assert_called_once_with(
+ FleetId="fleet-weighted",
+ TargetCapacitySpecification={"TotalTargetCapacity": 0},
+ )
+
+ def test_maintain_fleet_partial_return_uses_weighted_sum(self):
+ """Partial return: 1 of 2 c5.xlarge (weight=4) → new capacity = 8 - 4 = 4."""
+ active = [
+ {"InstanceId": "i-aaa", "InstanceType": "c5.xlarge"},
+ {"InstanceId": "i-bbb", "InstanceType": "c5.xlarge"},
+ ]
+ overrides = [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ ec2 = MagicMock()
+ # First describe call returns both instances (for _sum_weighted_capacity).
+ # Second call returns i-bbb still active (partial return → fleet not deleted).
+ ec2.describe_fleet_instances.side_effect = [
+ {"ActiveInstances": active},
+ {"ActiveInstances": [{"InstanceId": "i-bbb", "InstanceType": "c5.xlarge"}]},
+ ]
+ ec2.modify_fleet.return_value = {}
+ mgr = _make_ec2_release_manager(ec2_client_mock=ec2)
+ fleet_details = _fleet_details("maintain", 8, overrides)
+
+ mgr.release(
+ fleet_id="fleet-partial",
+ instance_ids=["i-aaa"],
+ fleet_details=fleet_details,
+ )
+
+ ec2.modify_fleet.assert_called_once_with(
+ FleetId="fleet-partial",
+ TargetCapacitySpecification={"TotalTargetCapacity": 4},
+ )
+
+
+# ---------------------------------------------------------------------------
+# SpotFleetReleaseManager._sum_weighted_capacity
+# ---------------------------------------------------------------------------
+
+
+class TestSpotFleetSumWeightedCapacity:
+ def _make_manager(self, active_response: dict) -> SpotFleetReleaseManager:
+ ec2 = MagicMock()
+ ec2.describe_spot_fleet_instances.return_value = active_response
+ return _make_spot_release_manager(ec2_client_mock=ec2)
+
+ def test_per_instance_weight_from_describe(self):
+ """Uses WeightedCapacity directly from ActiveInstances response."""
+ active_response = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s1", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ {"InstanceId": "i-s2", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ ]
+ }
+ fleet_config = _spot_fleet_config("maintain", 8, [])
+ mgr = self._make_manager(active_response)
+ result = mgr._sum_weighted_capacity("sfr-111", fleet_config, ["i-s1", "i-s2"])
+ assert result == 8
+
+ def test_mixed_per_instance_weights(self):
+ """Different weights per instance are summed correctly."""
+ active_response = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s1", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ {"InstanceId": "i-s2", "InstanceType": "m5.large", "WeightedCapacity": "2"},
+ ]
+ }
+ fleet_config = _spot_fleet_config("maintain", 6, [])
+ mgr = self._make_manager(active_response)
+ result = mgr._sum_weighted_capacity("sfr-222", fleet_config, ["i-s1", "i-s2"])
+ assert result == 6
+
+ def test_fallback_to_launch_spec_weight_when_instance_missing(self):
+ """Instance absent from ActiveInstances: falls back to LaunchSpecifications weight."""
+ active_response = {"ActiveInstances": []}
+ fleet_config = _spot_fleet_config(
+ "maintain", 4, [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ )
+ # The instance is in launch specs but gone from active → use type weight from spec.
+ # However since it's not in instance_type_by_id either, it defaults to 1.
+ # The per-instance map is empty; type map has c5.xlarge=4 but we can't resolve
+ # the instance to a type. Default must be 1.
+ mgr = self._make_manager(active_response)
+ result = mgr._sum_weighted_capacity("sfr-333", fleet_config, ["i-gone"])
+ assert result == 1
+
+ def test_launch_spec_weight_used_when_instance_has_no_weighted_capacity_field(self):
+ """Instance in ActiveInstances but WeightedCapacity field absent → type fallback."""
+ active_response = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s1", "InstanceType": "c5.xlarge"}, # no WeightedCapacity
+ ]
+ }
+ fleet_config = _spot_fleet_config(
+ "maintain", 4, [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ )
+ mgr = self._make_manager(active_response)
+ result = mgr._sum_weighted_capacity("sfr-444", fleet_config, ["i-s1"])
+ assert result == 4
+
+ def test_no_weights_anywhere_defaults_to_instance_count(self):
+ """No weights in describe or fleet config → sum equals instance count."""
+ active_response = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s1", "InstanceType": "t3.medium"},
+ {"InstanceId": "i-s2", "InstanceType": "t3.medium"},
+ ]
+ }
+ fleet_config = _spot_fleet_config("maintain", 2, [])
+ mgr = self._make_manager(active_response)
+ result = mgr._sum_weighted_capacity("sfr-555", fleet_config, ["i-s1", "i-s2"])
+ assert result == 2
+
+ def test_describe_error_defaults_all_to_1(self):
+ """If describe_spot_fleet_instances raises, all instances default to weight 1."""
+ ec2 = MagicMock()
+ ec2.describe_spot_fleet_instances.side_effect = Exception("throttled")
+ mgr = _make_spot_release_manager(ec2_client_mock=ec2)
+ fleet_config = _spot_fleet_config(
+ "maintain", 8, [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ )
+ result = mgr._sum_weighted_capacity("sfr-666", fleet_config, ["i-s1", "i-s2"])
+ assert result == 2 # 2 instances × default weight 1
+
+
+# ---------------------------------------------------------------------------
+# SpotFleetReleaseManager.release() — weighted path end-to-end
+# ---------------------------------------------------------------------------
+
+
+class TestSpotFleetReleaseWeighted:
+ def _make_fleet_details(self, fleet_type: str, target: int, specs: list[dict]) -> dict:
+ return {
+ "SpotFleetRequestConfig": {
+ "Type": fleet_type,
+ "TargetCapacity": target,
+ "OnDemandTargetCapacity": 0,
+ "LaunchSpecifications": specs,
+ "TagSpecifications": [],
+ },
+ "Tags": [{"Key": "orb:request-id", "Value": "req-spot-weighted"}],
+ }
+
+ def test_modify_spot_fleet_called_with_weighted_new_capacity(self):
+ """modify_spot_fleet_request receives target - sum(weights), not target - count."""
+ active_response = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s1", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ {"InstanceId": "i-s2", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ ]
+ }
+ ec2 = MagicMock()
+ # First call: _sum_weighted_capacity; second call: _fleet_has_no_remaining_instances
+ ec2.describe_spot_fleet_instances.side_effect = [
+ active_response,
+ {"ActiveInstances": []},
+ ]
+ ec2.modify_spot_fleet_request.return_value = {}
+ ec2.cancel_spot_fleet_requests.return_value = {
+ "SuccessfulFleetCancellations": [],
+ "UnsuccessfulFleetCancellations": [],
+ }
+ mgr = _make_spot_release_manager(ec2_client_mock=ec2)
+ fleet_details = self._make_fleet_details(
+ "maintain", 8, [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ )
+
+ mgr.release(
+ fleet_id="sfr-weighted",
+ instance_ids=["i-s1", "i-s2"],
+ fleet_details=fleet_details,
+ )
+
+ ec2.modify_spot_fleet_request.assert_called_once_with(
+ SpotFleetRequestId="sfr-weighted",
+ TargetCapacity=0,
+ OnDemandTargetCapacity=0,
+ )
+
+ def test_partial_spot_release_uses_weighted_sum(self):
+ """Partial return: 1 of 2 c5.xlarge (weight=4) → new capacity = 8 - 4 = 4."""
+ active_response_sum = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s1", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ {"InstanceId": "i-s2", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ ]
+ }
+ active_response_remaining = {
+ "ActiveInstances": [
+ {"InstanceId": "i-s2", "InstanceType": "c5.xlarge", "WeightedCapacity": "4"},
+ ]
+ }
+ ec2 = MagicMock()
+ ec2.describe_spot_fleet_instances.side_effect = [
+ active_response_sum,
+ active_response_remaining,
+ ]
+ ec2.modify_spot_fleet_request.return_value = {}
+ mgr = _make_spot_release_manager(ec2_client_mock=ec2)
+ fleet_details = self._make_fleet_details(
+ "maintain", 8, [{"InstanceType": "c5.xlarge", "WeightedCapacity": 4}]
+ )
+
+ mgr.release(
+ fleet_id="sfr-partial",
+ instance_ids=["i-s1"],
+ fleet_details=fleet_details,
+ )
+
+ ec2.modify_spot_fleet_request.assert_called_once_with(
+ SpotFleetRequestId="sfr-partial",
+ TargetCapacity=4,
+ OnDemandTargetCapacity=0,
+ )
diff --git a/tests/unit/providers/aws/infrastructure/handlers/test_run_instances_handler.py b/tests/providers/aws/unit/infrastructure/handlers/test_run_instances_handler.py
similarity index 67%
rename from tests/unit/providers/aws/infrastructure/handlers/test_run_instances_handler.py
rename to tests/providers/aws/unit/infrastructure/handlers/test_run_instances_handler.py
index 6724b261f..538ff8f83 100644
--- a/tests/unit/providers/aws/infrastructure/handlers/test_run_instances_handler.py
+++ b/tests/providers/aws/unit/infrastructure/handlers/test_run_instances_handler.py
@@ -1,4 +1,4 @@
-"""Unit tests for RunInstancesHandler.check_hosts_status."""
+"""Unit tests for RunInstancesHandler.check_hosts_status and ProviderFulfilment."""
from typing import Any
from unittest.mock import MagicMock, patch
@@ -6,6 +6,7 @@
import pytest
from botocore.exceptions import ClientError
+from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError, AWSValidationError
from orb.providers.aws.infrastructure.handlers.run_instances.handler import RunInstancesHandler
from orb.providers.aws.infrastructure.launch_template.manager import LTNetworkingState
@@ -21,13 +22,16 @@ def _make_handler() -> Any:
return handler
-def _make_request(resource_ids=None, instance_ids=None, provider_data=None, metadata=None):
+def _make_request(
+ resource_ids=None, instance_ids=None, provider_data=None, metadata=None, requested_count=2
+):
request = MagicMock()
request.request_id = "req-ri-123"
request.resource_ids = resource_ids or []
request.provider_api = "RunInstances"
request.provider_data = provider_data if provider_data is not None else {}
request.metadata = metadata if metadata is not None else {}
+ request.requested_count = requested_count
return request
@@ -56,29 +60,69 @@ def _formatted_instances(instance_ids, resource_id="r-test"):
class TestRunInstancesHandlerCheckHostsStatus:
- def test_check_hosts_status_all_running(self):
- """All running instances → returns all."""
+ def test_check_hosts_status_returns_check_hosts_status_result(self):
+ """check_hosts_status returns CheckHostsStatusResult (not a plain list)."""
handler = _make_handler()
instance_ids = ["i-run1", "i-run2"]
request = _make_request(
resource_ids=["r-res1"],
provider_data={"instance_ids": instance_ids, "reservation_id": "r-res1"},
+ requested_count=2,
)
+ with patch.object(
+ handler,
+ "_get_instance_details",
+ return_value=_formatted_instances(instance_ids, "r-res1"),
+ ):
+ with patch.object(
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
+ ):
+ result = handler.check_hosts_status(request)
+ assert isinstance(result, CheckHostsStatusResult)
+
+ def test_check_hosts_status_all_running_fulfilled(self):
+ """All running instances → fulfilment state == 'fulfilled'."""
+ handler = _make_handler()
+ instance_ids = ["i-run1", "i-run2"]
+ request = _make_request(
+ resource_ids=["r-res1"],
+ provider_data={"instance_ids": instance_ids, "reservation_id": "r-res1"},
+ requested_count=2,
+ )
+ with patch.object(
+ handler,
+ "_get_instance_details",
+ return_value=_formatted_instances(instance_ids, "r-res1"),
+ ):
+ with patch.object(
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
+ ):
+ result = handler.check_hosts_status(request)
+ assert len(result.instances) == 2
+ assert result.fulfilment.state == "fulfilled"
+ assert result.fulfilment.running_count == 2
+
+ def test_check_hosts_status_all_running_instances_ids(self):
+ """All running instances → instance IDs present."""
+ handler = _make_handler()
+ instance_ids = ["i-run1", "i-run2"]
+ request = _make_request(
+ resource_ids=["r-res1"],
+ provider_data={"instance_ids": instance_ids, "reservation_id": "r-res1"},
+ requested_count=2,
+ )
with patch.object(
handler,
"_get_instance_details",
return_value=_formatted_instances(instance_ids, "r-res1"),
):
with patch.object(
- handler,
- "_format_instance_data",
- side_effect=lambda insts, rid, api_val: insts,
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
):
result = handler.check_hosts_status(request)
- assert len(result) == 2
- returned_ids = {r["instance_id"] for r in result}
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(instance_ids)
def test_check_hosts_status_mixed_states(self):
@@ -88,31 +132,31 @@ def test_check_hosts_status_mixed_states(self):
request = _make_request(
resource_ids=["r-mixed"],
provider_data={"instance_ids": instance_ids, "reservation_id": "r-mixed"},
+ requested_count=3,
)
-
with patch.object(
handler,
"_get_instance_details",
return_value=_formatted_instances(instance_ids, "r-mixed"),
):
with patch.object(
- handler,
- "_format_instance_data",
- side_effect=lambda insts, rid, api_val: insts,
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
):
result = handler.check_hosts_status(request)
- assert len(result) == 3
+ assert len(result.instances) == 3
def test_check_hosts_status_empty_resource_ids(self):
- """No resource_ids and no instance_ids → returns []."""
+ """No resource_ids and no instance_ids → returns in_progress fulfilment."""
handler = _make_handler()
request = _make_request(resource_ids=[], provider_data={}, metadata={})
with patch.object(handler, "_get_instance_details") as mock_get:
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert result.instances == []
+ assert result.fulfilment.state == "in_progress"
mock_get.assert_not_called()
def test_check_hosts_status_aws_error(self):
@@ -123,7 +167,6 @@ def test_check_hosts_status_aws_error(self):
resource_ids=["r-err"],
provider_data={"instance_ids": instance_ids, "reservation_id": "r-err"},
)
-
with patch.object(
handler, "_get_instance_details", side_effect=_make_client_error("InternalError")
):
@@ -138,44 +181,40 @@ def test_check_hosts_status_falls_back_to_metadata_instance_ids(self):
resource_ids=["r-meta"],
provider_data={},
metadata={"instance_ids": instance_ids},
+ requested_count=2,
)
-
with patch.object(
handler,
"_get_instance_details",
return_value=_formatted_instances(instance_ids, "r-meta"),
):
with patch.object(
- handler,
- "_format_instance_data",
- side_effect=lambda insts, rid, api_val: insts,
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
):
result = handler.check_hosts_status(request)
- assert len(result) == 2
+ assert len(result.instances) == 2
def test_check_hosts_status_returns_correct_count(self):
- """Verify count matches instances returned."""
+ """Verify instance count in result matches instances returned."""
handler = _make_handler()
instance_ids = ["i-r1", "i-r2", "i-r3", "i-r4"]
request = _make_request(
resource_ids=["r-cnt"],
provider_data={"instance_ids": instance_ids, "reservation_id": "r-cnt"},
+ requested_count=4,
)
-
with patch.object(
handler,
"_get_instance_details",
return_value=_formatted_instances(instance_ids, "r-cnt"),
):
with patch.object(
- handler,
- "_format_instance_data",
- side_effect=lambda insts, rid, api_val: insts,
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
):
result = handler.check_hosts_status(request)
- assert len(result) == 4
+ assert len(result.instances) == 4
def test_check_hosts_status_preserves_instance_ids(self):
"""Instance IDs in result match input."""
@@ -184,61 +223,98 @@ def test_check_hosts_status_preserves_instance_ids(self):
request = _make_request(
resource_ids=["r-ids"],
provider_data={"instance_ids": instance_ids, "reservation_id": "r-ids"},
+ requested_count=2,
)
-
with patch.object(
handler,
"_get_instance_details",
return_value=_formatted_instances(instance_ids, "r-ids"),
):
with patch.object(
- handler,
- "_format_instance_data",
- side_effect=lambda insts, rid, api_val: insts,
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
):
result = handler.check_hosts_status(request)
- returned_ids = {r["instance_id"] for r in result}
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(instance_ids)
def test_check_hosts_status_state_filtering_is_strict(self):
"""Only instances returned by _get_instance_details appear in result."""
handler = _make_handler()
launched_ids = ["i-strict1", "i-strict2"]
- # Only one instance returned (the other was terminated/not found)
returned_by_aws = _formatted_instances(["i-strict1"], "r-strict")
request = _make_request(
resource_ids=["r-strict"],
provider_data={"instance_ids": launched_ids, "reservation_id": "r-strict"},
+ requested_count=2,
)
-
with patch.object(handler, "_get_instance_details", return_value=returned_by_aws):
with patch.object(
- handler,
- "_format_instance_data",
- side_effect=lambda insts, rid, api_val: insts,
+ handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
):
result = handler.check_hosts_status(request)
- assert len(result) == 1
- assert result[0]["instance_id"] == "i-strict1"
+ assert len(result.instances) == 1
+ assert result.instances[0]["instance_id"] == "i-strict1"
def test_check_hosts_status_uses_resource_ids_when_no_instance_ids(self):
"""Falls back to _find_instances_by_resource_ids when no instance_ids anywhere."""
handler = _make_handler()
- request = _make_request(
- resource_ids=["r-fallback"],
- provider_data={},
- metadata={},
- )
-
- fallback_result = _formatted_instances(["i-fb1"], "r-fallback")
+ request = _make_request(resource_ids=["r-fallback"], provider_data={}, metadata={})
+ from orb.domain.base.provider_fulfilment import ProviderFulfilment
+ fallback_result = CheckHostsStatusResult(
+ instances=_formatted_instances(["i-fb1"], "r-fallback"),
+ fulfilment=ProviderFulfilment(state="in_progress", message="test"),
+ )
with patch.object(handler, "_find_instances_by_resource_ids", return_value=fallback_result):
result = handler.check_hosts_status(request)
- assert len(result) == 1
- assert result[0]["instance_id"] == "i-fb1"
+ assert len(result.instances) == 1
+ assert result.instances[0]["instance_id"] == "i-fb1"
+
+
+class TestRunInstancesProviderFulfilment:
+ """Unit tests for RunInstances fulfilment computation."""
+
+ def _run(self, instances, requested_count):
+ handler = _make_handler()
+ return handler._compute_run_instances_fulfilment(instances, requested_count)
+
+ def _inst(self, status):
+ return {"instance_id": "i-x", "status": status}
+
+ def test_all_running_at_target_is_fulfilled(self):
+ instances = [self._inst("running"), self._inst("running")]
+ f = self._run(instances, 2)
+ assert f.state == "fulfilled"
+ assert f.running_count == 2
+
+ def test_running_exceeds_target_is_fulfilled(self):
+ instances = [self._inst("running")] * 3
+ f = self._run(instances, 2)
+ assert f.state == "fulfilled"
+
+ def test_pending_instances_is_in_progress(self):
+ instances = [self._inst("running"), self._inst("pending")]
+ f = self._run(instances, 2)
+ assert f.state == "in_progress"
+ assert f.pending_count == 1
+
+ def test_no_instances_is_in_progress(self):
+ f = self._run([], 2)
+ assert f.state == "in_progress"
+
+ def test_some_running_below_target_is_in_progress(self):
+ instances = [self._inst("running")]
+ f = self._run(instances, 2)
+ # 1 running, no pending → partial
+ assert f.state == "partial"
+
+ def test_all_failed_is_failed(self):
+ instances = [self._inst("failed"), self._inst("failed")]
+ f = self._run(instances, 2)
+ assert f.state == "failed"
class TestRunInstancesHandlerMachineAdapterContext:
@@ -250,6 +326,7 @@ def test_check_hosts_status_passes_context_to_get_instance_details(self):
request = _make_request(
resource_ids=[resource_id],
provider_data={"instance_ids": instance_ids, "reservation_id": resource_id},
+ requested_count=2,
)
with patch.object(handler, "_get_instance_details", return_value=[]) as mock_details:
diff --git a/tests/unit/providers/aws/infrastructure/handlers/test_spot_fleet_handler.py b/tests/providers/aws/unit/infrastructure/handlers/test_spot_fleet_handler.py
similarity index 66%
rename from tests/unit/providers/aws/infrastructure/handlers/test_spot_fleet_handler.py
rename to tests/providers/aws/unit/infrastructure/handlers/test_spot_fleet_handler.py
index 9acf8b23d..60411c44c 100644
--- a/tests/unit/providers/aws/infrastructure/handlers/test_spot_fleet_handler.py
+++ b/tests/providers/aws/unit/infrastructure/handlers/test_spot_fleet_handler.py
@@ -5,6 +5,11 @@
from botocore.exceptions import ClientError
+from orb.domain.base.provider_fulfilment import (
+ CheckHostsStatusResult,
+ FulfilmentState,
+ ProviderFulfilment,
+)
from orb.providers.aws.domain.template.aws_template_aggregate import AWSTemplate
from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError
from orb.providers.aws.infrastructure.handlers.spot_fleet.handler import SpotFleetHandler
@@ -20,11 +25,12 @@ def _make_handler() -> Any:
return handler
-def _make_request(resource_ids):
+def _make_request(resource_ids, requested_count: int = 3):
request = MagicMock()
request.request_id = "req-spot-123"
request.resource_ids = resource_ids
request.metadata = {}
+ request.requested_count = requested_count
return request
@@ -33,7 +39,7 @@ def _make_client_error(code="InternalError"):
def _formatted_instances(instance_ids, resource_id="sfr-test"):
- """Return already-formatted instance dicts (as _get_spot_fleet_instances returns them)."""
+ """Return already-formatted instance dicts."""
return [
{
"instance_id": iid,
@@ -52,170 +58,226 @@ def _formatted_instances(instance_ids, resource_id="sfr-test"):
]
+def _fleet_status_result(
+ instance_ids,
+ resource_id: str = "sfr-test",
+ state: FulfilmentState = "fulfilled",
+ target_units: int | None = None,
+ fulfilled_units: int | None = None,
+):
+ """Build a CheckHostsStatusResult for mocking _get_spot_fleet_status."""
+ instances = _formatted_instances(instance_ids, resource_id)
+ n = len(instance_ids)
+ return CheckHostsStatusResult(
+ instances=instances,
+ fulfilment=ProviderFulfilment(
+ state=state,
+ message="test",
+ target_units=target_units if target_units is not None else n,
+ fulfilled_units=fulfilled_units if fulfilled_units is not None else n,
+ running_count=n,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
+
+
class TestSpotFleetHandlerCheckHostsStatus:
def test_check_hosts_status_all_active(self):
- """All instances active → returns all."""
+ """All instances active → CheckHostsStatusResult with instances and fulfilled state."""
handler = _make_handler()
- request = _make_request(["sfr-111"])
instance_ids = ["i-s1", "i-s2", "i-s3"]
+ request = _make_request(["sfr-111"], requested_count=len(instance_ids))
with patch.object(
handler,
- "_get_spot_fleet_instances",
- return_value=_formatted_instances(instance_ids, "sfr-111"),
+ "_get_spot_fleet_status",
+ return_value=_fleet_status_result(instance_ids, "sfr-111"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 3
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ assert len(result.instances) == 3
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(instance_ids)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "fulfilled"
def test_check_hosts_status_partial_active(self):
- """AWS only returns active instances via describe_spot_fleet_instances — terminated excluded."""
+ """AWS only returns active instances — terminated excluded; result reflects active set."""
handler = _make_handler()
- request = _make_request(["sfr-222"])
active_ids = ["i-active1", "i-active2"]
+ request = _make_request(["sfr-222"], requested_count=4)
with patch.object(
handler,
- "_get_spot_fleet_instances",
- return_value=_formatted_instances(active_ids, "sfr-222"),
+ "_get_spot_fleet_status",
+ return_value=_fleet_status_result(
+ active_ids, "sfr-222", state="in_progress", target_units=4, fulfilled_units=2
+ ),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 2
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ assert len(result.instances) == 2
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(active_ids)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
def test_check_hosts_status_fleet_not_found(self):
- """_get_spot_fleet_instances returns [] when fleet not found → result is []."""
+ """_get_spot_fleet_status raises → error logged, skipped; result has empty instances."""
handler = _make_handler()
request = _make_request(["sfr-missing"])
- with patch.object(handler, "_get_spot_fleet_instances", return_value=[]):
+ with patch.object(
+ handler,
+ "_get_spot_fleet_status",
+ side_effect=AWSInfrastructureError("Fleet not found"),
+ ):
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert result.instances == []
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "in_progress"
def test_check_hosts_status_aws_error(self):
- """Exception inside per-fleet loop → logged and skipped; result is []."""
+ """Exception inside per-fleet loop → logged and skipped; result has empty instances."""
handler = _make_handler()
request = _make_request(["sfr-err"])
with patch.object(
- handler, "_get_spot_fleet_instances", side_effect=AWSInfrastructureError("AWS error")
+ handler,
+ "_get_spot_fleet_status",
+ side_effect=AWSInfrastructureError("AWS error"),
):
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert result.instances == []
+ assert isinstance(result.fulfilment, ProviderFulfilment)
def test_check_hosts_status_no_resource_ids(self):
- """Empty resource_ids → returns [] without calling AWS."""
+ """Empty resource_ids → returns CheckHostsStatusResult with empty instances, in_progress."""
handler = _make_handler()
request = _make_request([])
- with patch.object(handler, "_get_spot_fleet_instances") as mock_get:
+ with patch.object(handler, "_get_spot_fleet_status") as mock_get:
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert result.instances == []
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "in_progress"
mock_get.assert_not_called()
def test_check_hosts_status_returns_correct_count(self):
- """Verify count matches active instances."""
+ """Verify instance count in result matches instances returned."""
handler = _make_handler()
- request = _make_request(["sfr-cnt"])
instance_ids = ["i-c1", "i-c2", "i-c3", "i-c4"]
+ request = _make_request(["sfr-cnt"], requested_count=len(instance_ids))
with patch.object(
handler,
- "_get_spot_fleet_instances",
- return_value=_formatted_instances(instance_ids, "sfr-cnt"),
+ "_get_spot_fleet_status",
+ return_value=_fleet_status_result(instance_ids, "sfr-cnt"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 4
+ assert isinstance(result, CheckHostsStatusResult)
+ assert len(result.instances) == 4
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "fulfilled"
+ assert result.fulfilment.target_units == 4
+ assert result.fulfilment.fulfilled_units == 4
def test_check_hosts_status_preserves_instance_ids(self):
- """Instance IDs in result match input."""
+ """Instance IDs in result.instances match input."""
handler = _make_handler()
- request = _make_request(["sfr-ids"])
instance_ids = ["i-spot-preserve1", "i-spot-preserve2"]
+ request = _make_request(["sfr-ids"], requested_count=len(instance_ids))
with patch.object(
handler,
- "_get_spot_fleet_instances",
- return_value=_formatted_instances(instance_ids, "sfr-ids"),
+ "_get_spot_fleet_status",
+ return_value=_fleet_status_result(instance_ids, "sfr-ids"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == set(instance_ids)
+ assert isinstance(result.fulfilment, ProviderFulfilment)
def test_check_hosts_status_no_active_instances(self):
- """Fleet exists but has no active instances → returns []."""
+ """Fleet exists but has no active instances → empty instances, in_progress."""
handler = _make_handler()
request = _make_request(["sfr-empty"])
- with patch.object(handler, "_get_spot_fleet_instances", return_value=[]):
+ with patch.object(
+ handler,
+ "_get_spot_fleet_status",
+ return_value=CheckHostsStatusResult(
+ instances=[],
+ fulfilment=ProviderFulfilment(
+ state="in_progress",
+ message="Spot Fleet waiting for instances",
+ target_units=3,
+ fulfilled_units=0,
+ running_count=0,
+ pending_count=0,
+ failed_count=0,
+ ),
+ ),
+ ):
result = handler.check_hosts_status(request)
- assert result == []
+ assert isinstance(result, CheckHostsStatusResult)
+ assert result.instances == []
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "in_progress"
def test_check_hosts_status_multiple_fleets(self):
- """Multiple fleet IDs → aggregates results from all."""
+ """Multiple fleet IDs → aggregates instances from all; combined fulfilment state."""
handler = _make_handler()
- request = _make_request(["sfr-A", "sfr-B"])
+ request = _make_request(["sfr-A", "sfr-B"], requested_count=3)
ids_a = ["i-sa1", "i-sa2"]
ids_b = ["i-sb1"]
- def get_instances_side_effect(fleet_id, request_id):
+ def get_status_side_effect(fleet_id, request_id, requested_count):
if fleet_id == "sfr-A":
- return _formatted_instances(ids_a, "sfr-A")
- return _formatted_instances(ids_b, "sfr-B")
+ return _fleet_status_result(ids_a, "sfr-A")
+ return _fleet_status_result(ids_b, "sfr-B")
- with patch.object(
- handler, "_get_spot_fleet_instances", side_effect=get_instances_side_effect
- ):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ with patch.object(handler, "_get_spot_fleet_status", side_effect=get_status_side_effect):
+ result = handler.check_hosts_status(request)
- assert len(result) == 3
- returned_ids = {r["instance_id"] for r in result}
+ assert isinstance(result, CheckHostsStatusResult)
+ assert len(result.instances) == 3
+ returned_ids = {r["instance_id"] for r in result.instances}
assert returned_ids == {"i-sa1", "i-sa2", "i-sb1"}
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "fulfilled"
def test_check_hosts_status_state_filtering_is_strict(self):
- """Only instances returned by _get_spot_fleet_instances appear in result."""
+ """Only instances returned by _get_spot_fleet_status appear in result."""
handler = _make_handler()
- request = _make_request(["sfr-strict"])
active_ids = ["i-spot-strict-active"]
+ request = _make_request(["sfr-strict"], requested_count=1)
with patch.object(
handler,
- "_get_spot_fleet_instances",
- return_value=_formatted_instances(active_ids, "sfr-strict"),
+ "_get_spot_fleet_status",
+ return_value=_fleet_status_result(active_ids, "sfr-strict"),
):
- with patch.object(
- handler, "_format_instance_data", side_effect=lambda insts, rid, api_val: insts
- ):
- result = handler.check_hosts_status(request)
+ result = handler.check_hosts_status(request)
- assert len(result) == 1
- assert result[0]["instance_id"] == "i-spot-strict-active"
+ assert isinstance(result, CheckHostsStatusResult)
+ assert len(result.instances) == 1
+ assert result.instances[0]["instance_id"] == "i-spot-strict-active"
+ assert isinstance(result.fulfilment, ProviderFulfilment)
+ assert result.fulfilment.state == "fulfilled"
class TestSpotFleetHandlerNameTag:
diff --git a/tests/unit/providers/aws/infrastructure/test_machine_adapter.py b/tests/providers/aws/unit/infrastructure/test_machine_adapter.py
similarity index 100%
rename from tests/unit/providers/aws/infrastructure/test_machine_adapter.py
rename to tests/providers/aws/unit/infrastructure/test_machine_adapter.py
diff --git a/tests/unit/providers/aws/infrastructure/test_resource_tags.py b/tests/providers/aws/unit/infrastructure/test_resource_tags.py
similarity index 100%
rename from tests/unit/providers/aws/infrastructure/test_resource_tags.py
rename to tests/providers/aws/unit/infrastructure/test_resource_tags.py
diff --git a/tests/unit/providers/aws/utilities/ec2/__init__.py b/tests/providers/aws/unit/storage/__init__.py
similarity index 100%
rename from tests/unit/providers/aws/utilities/ec2/__init__.py
rename to tests/providers/aws/unit/storage/__init__.py
diff --git a/tests/unit/providers/aws/storage/test_aws_storage_config.py b/tests/providers/aws/unit/storage/test_aws_storage_config.py
similarity index 100%
rename from tests/unit/providers/aws/storage/test_aws_storage_config.py
rename to tests/providers/aws/unit/storage/test_aws_storage_config.py
diff --git a/tests/unit/providers/aws/storage/test_dynamodb_converter.py b/tests/providers/aws/unit/storage/test_dynamodb_converter.py
similarity index 100%
rename from tests/unit/providers/aws/storage/test_dynamodb_converter.py
rename to tests/providers/aws/unit/storage/test_dynamodb_converter.py
diff --git a/tests/unit/providers/aws/storage/test_registration_collections.py b/tests/providers/aws/unit/storage/test_registration_collections.py
similarity index 100%
rename from tests/unit/providers/aws/storage/test_registration_collections.py
rename to tests/providers/aws/unit/storage/test_registration_collections.py
diff --git a/tests/unit/providers/aws/storage/test_registration_logging.py b/tests/providers/aws/unit/storage/test_registration_logging.py
similarity index 100%
rename from tests/unit/providers/aws/storage/test_registration_logging.py
rename to tests/providers/aws/unit/storage/test_registration_logging.py
diff --git a/tests/unit/providers/aws/storage/test_registration_typed_config.py b/tests/providers/aws/unit/storage/test_registration_typed_config.py
similarity index 100%
rename from tests/unit/providers/aws/storage/test_registration_typed_config.py
rename to tests/providers/aws/unit/storage/test_registration_typed_config.py
diff --git a/tests/providers/aws/unit/strategy/__init__.py b/tests/providers/aws/unit/strategy/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/unit/providers/aws/strategy/test_augment_capacity_metadata.py b/tests/providers/aws/unit/strategy/test_augment_capacity_metadata.py
similarity index 100%
rename from tests/unit/providers/aws/strategy/test_augment_capacity_metadata.py
rename to tests/providers/aws/unit/strategy/test_augment_capacity_metadata.py
diff --git a/tests/unit/providers/aws/strategy/test_aws_provider_strategy_discovery.py b/tests/providers/aws/unit/strategy/test_aws_provider_strategy_discovery.py
similarity index 100%
rename from tests/unit/providers/aws/strategy/test_aws_provider_strategy_discovery.py
rename to tests/providers/aws/unit/strategy/test_aws_provider_strategy_discovery.py
diff --git a/tests/providers/aws/unit/strategy/test_operation_outcome.py b/tests/providers/aws/unit/strategy/test_operation_outcome.py
new file mode 100644
index 000000000..2115b9197
--- /dev/null
+++ b/tests/providers/aws/unit/strategy/test_operation_outcome.py
@@ -0,0 +1,217 @@
+"""Unit tests for OperationOutcome discriminated union and FollowUpContext types.
+
+Covers:
+- All four variants are instantiable and frozen (value-comparable)
+- assert_never compiles and covers all branches
+- FollowUpContext variants have the correct discriminator tags
+- AWSProviderStrategy implements acquire/return_machines/get_status
+"""
+
+from typing import assert_never
+
+import pytest
+
+from orb.domain.base.operation_outcome import (
+ Accepted,
+ Completed,
+ Failed,
+ OperationOutcome,
+ RequiresFollowUp,
+)
+
+# ---------------------------------------------------------------------------
+# Domain types
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+class TestOperationOutcomeVariants:
+ """All OperationOutcome variants must be frozen and value-comparable."""
+
+ def test_accepted_default_fields(self):
+ o = Accepted(request_id="req-1")
+ assert o.request_id == "req-1"
+ assert o.pending_resource_ids == []
+ assert o.metadata == {}
+
+ def test_accepted_with_pending_ids(self):
+ o = Accepted(request_id="req-1", pending_resource_ids=["i-1", "i-2"])
+ assert o.pending_resource_ids == ["i-1", "i-2"]
+
+ def test_completed_default_fields(self):
+ o = Completed()
+ assert o.resource_ids == []
+ assert o.metadata == {}
+
+ def test_completed_with_ids(self):
+ o = Completed(resource_ids=["i-1"])
+ assert o.resource_ids == ["i-1"]
+
+ def test_failed_default_fields(self):
+ o = Failed(error="something bad")
+ assert o.error == "something bad"
+ assert o.recoverable is False
+ assert o.metadata == {}
+
+ def test_failed_recoverable(self):
+ o = Failed(error="throttle", recoverable=True)
+ assert o.recoverable is True
+
+ def test_accepted_is_frozen(self):
+ o = Accepted(request_id="req-1")
+ with pytest.raises((AttributeError, TypeError)):
+ o.request_id = "req-2" # type: ignore[misc]
+
+ def test_completed_is_frozen(self):
+ o = Completed(resource_ids=["i-1"])
+ with pytest.raises((AttributeError, TypeError)):
+ o.resource_ids = [] # type: ignore[misc]
+
+ def test_failed_is_frozen(self):
+ o = Failed(error="bad")
+ with pytest.raises((AttributeError, TypeError)):
+ o.error = "other" # type: ignore[misc]
+
+ def test_accepted_value_equality(self):
+ a = Accepted(request_id="req-1", pending_resource_ids=["i-1"])
+ b = Accepted(request_id="req-1", pending_resource_ids=["i-1"])
+ assert a == b
+
+ def test_completed_value_equality(self):
+ a = Completed(resource_ids=["i-1", "i-2"])
+ b = Completed(resource_ids=["i-1", "i-2"])
+ assert a == b
+
+
+@pytest.mark.unit
+class TestFollowUpContext:
+ """FollowUpContext variants must carry the correct discriminator tags."""
+
+ def test_termination_context_kind(self):
+ from orb.application.services.request_follow_up_context import (
+ TerminationFollowUpContext,
+ )
+
+ ctx = TerminationFollowUpContext(pending_instance_ids=["i-1"])
+ assert ctx.follow_up_kind == "termination"
+ assert ctx.expected_terminal_state == "terminated"
+ assert ctx.pending_instance_ids == ["i-1"]
+
+ def test_deployment_polling_context_kind(self):
+ from orb.application.services.request_follow_up_context import (
+ DeploymentPollingFollowUpContext,
+ )
+
+ ctx = DeploymentPollingFollowUpContext(pending_resource_ids=["fleet-1"])
+ assert ctx.follow_up_kind == "deployment_polling"
+ assert ctx.expected_terminal_state == "running"
+ assert ctx.pending_resource_ids == ["fleet-1"]
+
+ def test_requires_follow_up_wraps_context(self):
+ from orb.application.services.request_follow_up_context import (
+ TerminationFollowUpContext,
+ )
+
+ ctx = TerminationFollowUpContext()
+ outcome: OperationOutcome = RequiresFollowUp(context=ctx)
+ assert isinstance(outcome, RequiresFollowUp)
+ assert outcome.context.follow_up_kind == "termination"
+
+ def test_termination_context_is_frozen(self):
+ from orb.application.services.request_follow_up_context import (
+ TerminationFollowUpContext,
+ )
+
+ ctx = TerminationFollowUpContext()
+ with pytest.raises((AttributeError, TypeError)):
+ ctx.expected_terminal_state = "stopped" # type: ignore[misc]
+
+
+@pytest.mark.unit
+class TestAssertNeverExhaustiveness:
+ """Exhaustive match over OperationOutcome must not raise and cover all branches."""
+
+ def _classify(self, outcome: OperationOutcome) -> str:
+ match outcome:
+ case Accepted():
+ return "accepted"
+ case Completed():
+ return "completed"
+ case RequiresFollowUp():
+ return "requires_follow_up"
+ case Failed():
+ return "failed"
+ case _ as unreachable:
+ assert_never(unreachable)
+
+ def test_accepted_classified(self):
+ assert self._classify(Accepted(request_id="r")) == "accepted"
+
+ def test_completed_classified(self):
+ assert self._classify(Completed()) == "completed"
+
+ def test_failed_classified(self):
+ assert self._classify(Failed(error="e")) == "failed"
+
+ def test_requires_follow_up_classified(self):
+ from orb.application.services.request_follow_up_context import TerminationFollowUpContext
+
+ ctx = TerminationFollowUpContext()
+ assert self._classify(RequiresFollowUp(context=ctx)) == "requires_follow_up"
+
+
+# ---------------------------------------------------------------------------
+# AWSProviderStrategy — interface contract
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+class TestAWSProviderStrategyOutcomeInterface:
+ """AWSProviderStrategy must declare acquire / return_machines / get_status."""
+
+ def test_acquire_method_exists(self):
+
+ from orb.providers.aws.strategy.aws_provider_strategy import AWSProviderStrategy
+
+ assert hasattr(AWSProviderStrategy, "acquire")
+ assert asyncio_or_coroutine(AWSProviderStrategy.acquire)
+
+ def test_return_machines_method_exists(self):
+ from orb.providers.aws.strategy.aws_provider_strategy import AWSProviderStrategy
+
+ assert hasattr(AWSProviderStrategy, "return_machines")
+ assert asyncio_or_coroutine(AWSProviderStrategy.return_machines)
+
+ def test_get_status_method_exists(self):
+ from orb.providers.aws.strategy.aws_provider_strategy import AWSProviderStrategy
+
+ assert hasattr(AWSProviderStrategy, "get_status")
+ assert asyncio_or_coroutine(AWSProviderStrategy.get_status)
+
+ def test_base_strategy_declares_acquire_abstract(self):
+
+ from orb.providers.base.strategy.base_provider_strategy import BaseProviderStrategy
+
+ assert "acquire" in {m for m in dir(BaseProviderStrategy)}
+ method = getattr(BaseProviderStrategy, "acquire")
+ assert getattr(method, "__isabstractmethod__", False)
+
+ def test_base_strategy_declares_return_machines_abstract(self):
+ from orb.providers.base.strategy.base_provider_strategy import BaseProviderStrategy
+
+ method = getattr(BaseProviderStrategy, "return_machines")
+ assert getattr(method, "__isabstractmethod__", False)
+
+ def test_base_strategy_declares_get_status_abstract(self):
+ from orb.providers.base.strategy.base_provider_strategy import BaseProviderStrategy
+
+ method = getattr(BaseProviderStrategy, "get_status")
+ assert getattr(method, "__isabstractmethod__", False)
+
+
+def asyncio_or_coroutine(fn) -> bool:
+ """Return True if fn is an async function."""
+ import asyncio
+ import inspect
+
+ return asyncio.iscoroutinefunction(fn) or inspect.iscoroutinefunction(fn)
diff --git a/tests/unit/providers/aws/test_allocation_strategy.py b/tests/providers/aws/unit/test_allocation_strategy.py
similarity index 99%
rename from tests/unit/providers/aws/test_allocation_strategy.py
rename to tests/providers/aws/unit/test_allocation_strategy.py
index 167b4d357..4f95dc27e 100644
--- a/tests/unit/providers/aws/test_allocation_strategy.py
+++ b/tests/providers/aws/unit/test_allocation_strategy.py
@@ -2,7 +2,7 @@
import pytest
-from orb.providers.aws.domain.template.value_objects import (
+from orb.providers.aws.value_objects import (
AWSAllocationStrategy,
normalise_allocation_strategy,
)
diff --git a/tests/providers/aws/unit/test_aws_error_propagation.py b/tests/providers/aws/unit/test_aws_error_propagation.py
new file mode 100644
index 000000000..2cc063867
--- /dev/null
+++ b/tests/providers/aws/unit/test_aws_error_propagation.py
@@ -0,0 +1,382 @@
+"""Unit tests: AWS ClientError details are captured and propagated to the request.
+
+Covers:
+ - _convert_client_error preserves aws_error_code, aws_error_message,
+ aws_request_id, error_source on every mapped exception subclass.
+ - Access-key redaction in error messages.
+ - _extract_aws_error_fields helper extracts attrs from AWSError subclasses
+ and returns None values for non-AWSError exceptions.
+ - ProvisioningResult carries AWS error fields through from the exception.
+ - RequestStatusManagementService._handle_provisioning_failure writes
+ error_details["provider_error"] onto the Request aggregate.
+ - RequestDTO.from_domain exposes error_details["provider_error"] (or legacy
+ "aws_error") as the top-level error field; to_dict includes it only when present.
+"""
+
+from __future__ import annotations
+
+from unittest.mock import MagicMock
+
+from botocore.exceptions import ClientError
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _make_client_error(
+ code: str,
+ message: str,
+ request_id: str = "req-abc-123",
+ http_status: int = 403,
+) -> ClientError:
+ """Build a botocore ClientError with realistic response metadata."""
+ return ClientError(
+ {
+ "Error": {"Code": code, "Message": message},
+ "ResponseMetadata": {
+ "RequestId": request_id,
+ "HTTPStatusCode": http_status,
+ },
+ },
+ "RunInstances",
+ )
+
+
+def _make_handler():
+ """Minimal AWSHandler-like object exposing _convert_client_error."""
+ from orb.providers.aws.infrastructure.handlers.base_handler import AWSHandler
+
+ # AWSHandler is abstract; build via a minimal concrete subclass
+ class _Handler(AWSHandler):
+ def _acquire_hosts_internal(self, request, aws_template):
+ pass # type: ignore[return]
+
+ def check_hosts_status(self, request):
+ pass # type: ignore[return]
+
+ def release_hosts(self, machine_ids, resource_mapping=None, request_id=""):
+ pass
+
+ def cancel_resource(self, resource_id, request_id):
+ pass # type: ignore[return]
+
+ @classmethod
+ def get_example_templates(cls):
+ return []
+
+ return _Handler(
+ aws_client=MagicMock(),
+ logger=MagicMock(),
+ aws_ops=MagicMock(),
+ launch_template_manager=MagicMock(),
+ )
+
+
+# ---------------------------------------------------------------------------
+# _convert_client_error — error field propagation
+# ---------------------------------------------------------------------------
+
+
+class TestConvertClientError:
+ """_convert_client_error transfers AWS error metadata to the domain exception."""
+
+ def setup_method(self):
+ self.handler = _make_handler()
+
+ def _check_aws_fields(self, exc, expected_code, expected_message, expected_request_id):
+ assert getattr(exc, "aws_error_code", None) == expected_code
+ assert getattr(exc, "aws_error_message", None) == expected_message
+ assert getattr(exc, "aws_request_id", None) == expected_request_id
+ assert getattr(exc, "error_source", None) is not None
+
+ def test_unauthorized_operation_carries_aws_fields(self):
+ err = _make_client_error("UnauthorizedOperation", "You are not authorized.", "rid-001")
+ exc = self.handler._convert_client_error(err, "run_instances")
+ self._check_aws_fields(exc, "UnauthorizedOperation", "You are not authorized.", "rid-001")
+
+ def test_access_denied_carries_aws_fields(self):
+ err = _make_client_error("AccessDenied", "Access denied.", "rid-002")
+ exc = self.handler._convert_client_error(err, "create_fleet")
+ self._check_aws_fields(exc, "AccessDenied", "Access denied.", "rid-002")
+
+ def test_insufficient_instance_capacity_carries_aws_fields(self):
+ err = _make_client_error(
+ "InsufficientInstanceCapacity", "Insufficient capacity.", "rid-003"
+ )
+ exc = self.handler._convert_client_error(err, "run_instances")
+ # InsufficientInstanceCapacity falls to the AWSInfrastructureError else-branch
+ # which also carries AWS error fields.
+ self._check_aws_fields(
+ exc, "InsufficientInstanceCapacity", "Insufficient capacity.", "rid-003"
+ )
+
+ def test_quota_exceeded_carries_aws_fields(self):
+ err = _make_client_error("InstanceLimitExceeded", "Limit exceeded.", "rid-004")
+ exc = self.handler._convert_client_error(err, "run_instances")
+ self._check_aws_fields(exc, "InstanceLimitExceeded", "Limit exceeded.", "rid-004")
+
+ def test_error_source_contains_operation_name(self):
+ err = _make_client_error("UnauthorizedOperation", "Denied.", "rid-005")
+ exc = self.handler._convert_client_error(err, "run_instances")
+ assert "run_instances" in (getattr(exc, "error_source", "") or "")
+
+ def test_missing_response_metadata_does_not_raise(self):
+ """Gracefully handle ClientError without ResponseMetadata.RequestId."""
+ client_err = ClientError(
+ {"Error": {"Code": "UnauthorizedOperation", "Message": "Denied."}},
+ "RunInstances",
+ )
+ exc = self.handler._convert_client_error(client_err, "run_instances")
+ # aws_request_id should be None (key absent) but must not raise
+ assert getattr(exc, "aws_request_id", "sentinel") is None
+
+ def test_access_key_redacted_in_message(self):
+ """AWS error messages containing access-key tokens are redacted."""
+ message = "User AKIAIOSFODNN7EXAMPLE is not authorized to perform this operation."
+ err = _make_client_error("UnauthorizedOperation", message, "rid-006")
+ exc = self.handler._convert_client_error(err, "run_instances")
+ assert "AKIAIOSFODNN7EXAMPLE" not in str(exc)
+ assert getattr(exc, "aws_error_message", "") is not None
+ assert "AKIAIOSFODNN7EXAMPLE" not in (getattr(exc, "aws_error_message", "") or "")
+
+ def test_sts_access_key_prefix_redacted(self):
+ """Temporary credential key IDs (ASIA prefix) are also redacted."""
+ message = "ASIAIOSFODNN7EXAMPLE is invalid."
+ err = _make_client_error("AccessDenied", message, "rid-007")
+ exc = self.handler._convert_client_error(err, "run_instances")
+ assert "ASIAIOSFODNN7EXAMPLE" not in (getattr(exc, "aws_error_message", "") or "")
+
+
+# ---------------------------------------------------------------------------
+# _extract_aws_error_fields helper
+# ---------------------------------------------------------------------------
+
+
+class TestExtractAwsErrorFields:
+ """_extract_aws_error_fields returns correct dict for AWSError and falls back for others."""
+
+ def test_returns_aws_fields_from_aws_error(self):
+ from orb.application.services.provisioning_orchestration_service import (
+ _extract_aws_error_fields,
+ )
+ from orb.providers.aws.exceptions.aws_exceptions import AuthorizationError
+
+ exc = AuthorizationError(
+ "denied",
+ aws_error_code="UnauthorizedOperation",
+ aws_error_message="You are not authorized.",
+ aws_request_id="rid-xyz",
+ error_source="aws.ec2.run_instances",
+ )
+ result = _extract_aws_error_fields(exc)
+ assert result["provider_error_code"] == "UnauthorizedOperation"
+ assert result["provider_error_message"] == "You are not authorized."
+ assert result["provider_request_id"] == "rid-xyz"
+ assert result["error_source"] == "aws.ec2.run_instances"
+
+ def test_returns_none_values_for_generic_exception(self):
+ from orb.application.services.provisioning_orchestration_service import (
+ _extract_aws_error_fields,
+ )
+
+ result = _extract_aws_error_fields(ValueError("some error"))
+ assert result["provider_error_code"] is None
+ assert result["provider_error_message"] is None
+ assert result["provider_request_id"] is None
+ assert result["error_source"] is None
+
+ def test_partial_aws_error_attrs(self):
+ from orb.application.services.provisioning_orchestration_service import (
+ _extract_aws_error_fields,
+ )
+ from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError
+
+ exc = AWSInfrastructureError(
+ "infra error",
+ aws_error_code="InsufficientInstanceCapacity",
+ )
+ result = _extract_aws_error_fields(exc)
+ assert result["provider_error_code"] == "InsufficientInstanceCapacity"
+ assert result["provider_error_message"] is None
+
+
+# ---------------------------------------------------------------------------
+# RequestStatusManagementService._handle_provisioning_failure
+# ---------------------------------------------------------------------------
+
+
+class TestHandleProvisioningFailure:
+ """error_details["provider_error"] is written when provisioning fails with AWS context."""
+
+ def _make_service(self):
+ from orb.application.services.request_status_management_service import (
+ RequestStatusManagementService,
+ )
+
+ return RequestStatusManagementService(uow_factory=MagicMock(), logger=MagicMock())
+
+ def _make_provisioning_result(
+ self,
+ *,
+ success: bool = False,
+ resource_ids: list[str] | None = None,
+ machine_ids: list[str] | None = None,
+ instances: list[dict] | None = None,
+ provider_data: dict | None = None,
+ error_message: str | None = "Provisioning failed",
+ aws_error_code: str | None = None,
+ aws_error_message: str | None = None,
+ aws_request_id: str | None = None,
+ error_source: str | None = None,
+ ):
+ from orb.application.services.provisioning_orchestration_service import ProvisioningResult
+
+ return ProvisioningResult(
+ success=success,
+ resource_ids=resource_ids if resource_ids is not None else [],
+ machine_ids=machine_ids if machine_ids is not None else [],
+ instances=instances if instances is not None else [],
+ provider_data=provider_data if provider_data is not None else {},
+ error_message=error_message,
+ provider_error_code=aws_error_code,
+ provider_error_message=aws_error_message,
+ provider_request_id=aws_request_id,
+ error_source=error_source,
+ )
+
+ def _make_real_request(self):
+ """Build a real Request aggregate (not a mock) so model_copy works."""
+ from orb.domain.request.aggregate import Request
+ from orb.domain.request.value_objects import RequestType
+
+ # Let Request generate its own UUID-based ID
+ return Request.create_new_request(
+ request_type=RequestType.ACQUIRE,
+ template_id="tpl-test",
+ machine_count=1,
+ provider_type="aws",
+ )
+
+ def test_no_aws_fields_leaves_error_details_clean(self):
+ """When no AWS-specific fields present, error_details stays clean."""
+ svc = self._make_service()
+ request = self._make_real_request()
+ result = self._make_provisioning_result(error_message="generic error")
+ updated = svc._handle_provisioning_failure(request, result)
+ # aws_error key should be absent when no AWS details are available
+ assert "provider_error" not in updated.error_details
+
+ def test_aws_error_code_written_to_error_details(self):
+ """UnauthorizedOperation is stored in error_details["provider_error"]["code"]."""
+ svc = self._make_service()
+ request = self._make_real_request()
+ result = self._make_provisioning_result(
+ error_message="AWS error",
+ aws_error_code="UnauthorizedOperation",
+ aws_error_message="You are not authorized.",
+ aws_request_id="rid-aws-001",
+ error_source="aws.ec2.run_instances",
+ )
+ updated = svc._handle_provisioning_failure(request, result)
+ aws_err = updated.error_details.get("provider_error", {})
+ assert aws_err["code"] == "UnauthorizedOperation"
+ assert aws_err["message"] == "You are not authorized."
+ assert aws_err["aws_request_id"] == "rid-aws-001"
+ assert aws_err["source"] == "aws.ec2.run_instances"
+
+ def test_partial_aws_fields_stored(self):
+ """Only non-None AWS fields are stored — missing ones are absent from the block."""
+ svc = self._make_service()
+ request = self._make_real_request()
+ result = self._make_provisioning_result(
+ error_message="AWS error",
+ aws_error_code="InsufficientInstanceCapacity",
+ # aws_error_message, aws_request_id, error_source intentionally absent
+ )
+ updated = svc._handle_provisioning_failure(request, result)
+ aws_err = updated.error_details.get("provider_error", {})
+ assert aws_err["code"] == "InsufficientInstanceCapacity"
+ assert "message" not in aws_err
+ assert "aws_request_id" not in aws_err
+
+
+# ---------------------------------------------------------------------------
+# RequestDTO.from_domain — error field population
+# ---------------------------------------------------------------------------
+
+
+class TestRequestDTOErrorField:
+ """RequestDTO.from_domain exposes aws_error as the top-level error field."""
+
+ def _make_request_with_error(self, aws_error_block: dict):
+
+ from orb.domain.request.aggregate import Request
+ from orb.domain.request.value_objects import RequestType
+
+ request = Request.create_new_request(
+ request_type=RequestType.ACQUIRE,
+ template_id="tpl-1",
+ machine_count=1,
+ provider_type="aws",
+ )
+ # inject error_details
+ from orb.domain.request.request_types import RequestStatus
+
+ updated_fields = request.model_dump()
+ updated_fields["error_details"] = {"aws_error": aws_error_block}
+ updated_fields["status"] = RequestStatus.FAILED
+ return Request.model_validate(updated_fields)
+
+ def test_error_field_present_when_aws_error_in_details(self):
+ from orb.application.request.dto import RequestDTO
+
+ request = self._make_request_with_error(
+ {"code": "UnauthorizedOperation", "message": "not authorized"}
+ )
+ dto = RequestDTO.from_domain(request)
+ assert dto.error is not None
+ assert dto.error["code"] == "UnauthorizedOperation"
+ assert dto.error["message"] == "not authorized"
+
+ def test_error_field_none_when_no_aws_error(self):
+
+ from orb.application.request.dto import RequestDTO
+ from orb.domain.request.aggregate import Request
+ from orb.domain.request.value_objects import RequestType
+
+ request = Request.create_new_request(
+ request_type=RequestType.ACQUIRE,
+ template_id="tpl-1",
+ machine_count=1,
+ provider_type="aws",
+ )
+ dto = RequestDTO.from_domain(request)
+ assert dto.error is None
+
+ def test_to_dict_includes_error_block_when_present(self):
+ from orb.application.request.dto import RequestDTO
+
+ request = self._make_request_with_error(
+ {"code": "AccessDenied", "source": "aws.ec2.run_instances"}
+ )
+ dto = RequestDTO.from_domain(request)
+ d = dto.to_dict()
+ assert "error" in d
+ assert d["error"]["code"] == "AccessDenied"
+
+ def test_to_dict_omits_error_key_when_no_error(self):
+
+ from orb.application.request.dto import RequestDTO
+ from orb.domain.request.aggregate import Request
+ from orb.domain.request.value_objects import RequestType
+
+ request = Request.create_new_request(
+ request_type=RequestType.ACQUIRE,
+ template_id="tpl-1",
+ machine_count=1,
+ provider_type="aws",
+ )
+ dto = RequestDTO.from_domain(request)
+ d = dto.to_dict()
+ assert "error" not in d
diff --git a/tests/unit/providers/test_aws_handlers.py b/tests/providers/aws/unit/test_aws_handlers.py
similarity index 100%
rename from tests/unit/providers/test_aws_handlers.py
rename to tests/providers/aws/unit/test_aws_handlers.py
diff --git a/tests/unit/providers/aws/test_aws_native_spec_service.py b/tests/providers/aws/unit/test_aws_native_spec_service.py
similarity index 100%
rename from tests/unit/providers/aws/test_aws_native_spec_service.py
rename to tests/providers/aws/unit/test_aws_native_spec_service.py
diff --git a/tests/unit/providers/aws/test_aws_native_spec_service_merge.py b/tests/providers/aws/unit/test_aws_native_spec_service_merge.py
similarity index 100%
rename from tests/unit/providers/aws/test_aws_native_spec_service_merge.py
rename to tests/providers/aws/unit/test_aws_native_spec_service_merge.py
diff --git a/tests/unit/providers/aws/test_aws_provider_config_legacy_timeout.py b/tests/providers/aws/unit/test_aws_provider_config_legacy_timeout.py
similarity index 100%
rename from tests/unit/providers/aws/test_aws_provider_config_legacy_timeout.py
rename to tests/providers/aws/unit/test_aws_provider_config_legacy_timeout.py
diff --git a/tests/unit/providers/aws/test_base_context_mixin.py b/tests/providers/aws/unit/test_base_context_mixin.py
similarity index 100%
rename from tests/unit/providers/aws/test_base_context_mixin.py
rename to tests/providers/aws/unit/test_base_context_mixin.py
diff --git a/tests/providers/aws/unit/test_cognito_strategy.py b/tests/providers/aws/unit/test_cognito_strategy.py
new file mode 100644
index 000000000..3c3c097a5
--- /dev/null
+++ b/tests/providers/aws/unit/test_cognito_strategy.py
@@ -0,0 +1,453 @@
+"""Unit tests for CognitoAuthStrategy."""
+
+from __future__ import annotations
+
+import time
+from base64 import urlsafe_b64encode
+from typing import Any
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from orb.infrastructure.adapters.ports.auth import AuthContext, AuthStatus
+
+
+def _make_context(**kwargs) -> AuthContext:
+ defaults: dict[str, Any] = {
+ "method": "GET",
+ "path": "/api/v1/machines",
+ "headers": {},
+ "query_params": {},
+ }
+ defaults.update(kwargs)
+ return AuthContext(**defaults)
+
+
+def _make_logger() -> MagicMock:
+ logger = MagicMock()
+ logger.debug = MagicMock()
+ logger.info = MagicMock()
+ logger.warning = MagicMock()
+ logger.error = MagicMock()
+ return logger
+
+
+def _make_strategy(enabled: bool = True) -> Any:
+ from orb.providers.aws.auth.cognito_strategy import CognitoAuthStrategy
+
+ logger = _make_logger()
+
+ # Clear the class-level JWKS cache so each test starts from a clean state.
+ CognitoAuthStrategy._jwks_cache.clear()
+
+ with patch("boto3.client") as _mock_boto3:
+ _mock_boto3.return_value = MagicMock()
+ strategy = CognitoAuthStrategy(
+ logger=logger,
+ user_pool_id="us-east-1_EXAMPLE",
+ client_id="abc123",
+ region="us-east-1",
+ enabled=enabled,
+ )
+
+ return strategy
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — missing Authorization header
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_authenticate_missing_header():
+ """Cognito authenticate returns FAILED when Authorization header is missing."""
+ strategy = _make_strategy()
+ result = await strategy.authenticate(_make_context(headers={}))
+
+ assert result.status == AuthStatus.FAILED
+ assert "authorization" in (result.error_message or "").lower()
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — disabled strategy
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_authenticate_disabled():
+ """Cognito authenticate returns FAILED when strategy is disabled."""
+ strategy = _make_strategy(enabled=False)
+
+ result = await strategy.authenticate(
+ _make_context(headers={"authorization": "Bearer sometoken"})
+ )
+
+ assert result.status == AuthStatus.FAILED
+ assert "disabled" in (result.error_message or "").lower()
+
+
+# ---------------------------------------------------------------------------
+# validate_token() — expired token
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_validate_token_expired():
+ """validate_token returns EXPIRED for an expired JWT."""
+ import jwt as pyjwt
+
+ strategy = _make_strategy()
+
+ with patch.object(strategy, "_get_public_key", return_value="fake_key"):
+ with patch("orb.providers.aws.auth.cognito_strategy.jwt.decode") as mock_decode:
+ mock_decode.side_effect = pyjwt.ExpiredSignatureError("expired")
+ # Need an unverified header
+ with patch(
+ "orb.providers.aws.auth.cognito_strategy.jwt.get_unverified_header",
+ return_value={"kid": "test-kid"},
+ ):
+ result = await strategy.validate_token("expired.token.here")
+
+ assert result.status == AuthStatus.EXPIRED
+
+
+# ---------------------------------------------------------------------------
+# validate_token() — no key ID in header
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_validate_token_missing_kid():
+ """validate_token returns INVALID when token header has no kid."""
+
+ strategy = _make_strategy()
+
+ with patch(
+ "orb.providers.aws.auth.cognito_strategy.jwt.get_unverified_header",
+ return_value={}, # no "kid"
+ ):
+ result = await strategy.validate_token("some.token.here")
+
+ assert result.status == AuthStatus.INVALID
+
+
+# ---------------------------------------------------------------------------
+# _map_groups_to_roles
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_cognito_map_groups_to_roles_admin():
+ """Admin group maps to admin role."""
+ strategy = _make_strategy()
+ roles = strategy._map_groups_to_roles(["admin"])
+ assert "admin" in roles
+
+
+@pytest.mark.unit
+def test_cognito_map_groups_to_roles_unknown_group():
+ """Unknown groups do not add extra roles beyond the default user role."""
+ strategy = _make_strategy()
+ roles = strategy._map_groups_to_roles(["unknown-group"])
+ assert roles == ["user"]
+
+
+# ---------------------------------------------------------------------------
+# from_auth_config classmethod
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_cognito_from_auth_config_defaults():
+ """from_auth_config builds strategy with defaults when no sub-config given."""
+ from orb.config.schemas.server_schema import AuthConfig
+
+ auth_config = AuthConfig(strategy="cognito") # type: ignore[call-arg] # pydantic default fields
+
+ with patch("boto3.client") as _mock:
+ _mock.return_value = MagicMock()
+ from orb.providers.aws.auth.cognito_strategy import CognitoAuthStrategy
+
+ strategy = CognitoAuthStrategy.from_auth_config(auth_config)
+
+ assert strategy.region == "us-east-1"
+ assert strategy.user_pool_id == ""
+ assert strategy.client_id == ""
+
+
+# ---------------------------------------------------------------------------
+# _get_public_key — non-RSA key raises InvalidTokenError
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_get_public_key_non_rsa_raises():
+ """_get_public_key raises InvalidTokenError when the matched JWK is not RSA."""
+ import jwt as pyjwt
+
+ strategy = _make_strategy()
+
+ ec_jwks = {
+ "keys": [
+ {
+ "kid": "test-kid",
+ "kty": "EC",
+ "crv": "P-256",
+ "x": "abc",
+ "y": "def",
+ }
+ ]
+ }
+
+ mock_response = MagicMock()
+ mock_response.json.return_value = ec_jwks
+
+ with patch("requests.get", return_value=mock_response):
+ with pytest.raises(pyjwt.InvalidTokenError):
+ await strategy._get_public_key("test-kid")
+
+
+# ---------------------------------------------------------------------------
+# validate_token — full round-trip with a real RS256 token
+# ---------------------------------------------------------------------------
+
+
+def _generate_rs256_jwks_and_token(
+ user_pool_id: str,
+ client_id: str,
+ region: str,
+ kid: str = "test-key-1",
+) -> tuple[dict[str, Any], str]:
+ """
+ Generate a minimal JWKS dict and a matching RS256 JWT for testing.
+
+ Returns (jwks_dict, encoded_token).
+ """
+ from cryptography.hazmat.primitives.asymmetric import rsa
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
+
+ # Generate a 2048-bit RSA key pair
+ private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
+ public_key = private_key.public_key()
+ pub_numbers: RSAPublicNumbers = public_key.public_numbers() # type: ignore[attr-defined]
+
+ def _int_to_b64url(n: int) -> str:
+ byte_length = (n.bit_length() + 7) // 8
+ raw = n.to_bytes(byte_length, "big")
+ return urlsafe_b64encode(raw).rstrip(b"=").decode()
+
+ jwks: dict[str, Any] = {
+ "keys": [
+ {
+ "kid": kid,
+ "kty": "RSA",
+ "alg": "RS256",
+ "use": "sig",
+ "n": _int_to_b64url(pub_numbers.n),
+ "e": _int_to_b64url(pub_numbers.e),
+ }
+ ]
+ }
+
+ now = int(time.time())
+ payload = {
+ "sub": "user-cognito-123",
+ "cognito:username": "testuser",
+ "email": "test@example.com",
+ "cognito:groups": ["operators"],
+ "token_use": "access",
+ "aud": client_id,
+ "iss": f"https://cognito-idp.{region}.amazonaws.com/{user_pool_id}",
+ "iat": now,
+ "exp": now + 3600,
+ }
+
+ import jwt as pyjwt
+
+ token = pyjwt.encode(
+ payload,
+ private_key,
+ algorithm="RS256",
+ headers={"kid": kid},
+ )
+ return jwks, token
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_validate_token_full_rs256_round_trip():
+ """
+ Full validate_token round-trip: mocked JWKS + real RS256 token must succeed.
+
+ This test would fail if _get_public_key returned a raw JWK dict (the pre-fix
+ behaviour) because PyJWT cannot decode with a dict key.
+ """
+ user_pool_id = "us-east-1_TESTPOOL"
+ client_id = "test-client-id"
+ region = "us-east-1"
+
+ jwks, token = _generate_rs256_jwks_and_token(user_pool_id, client_id, region)
+
+ mock_response = MagicMock()
+ mock_response.json.return_value = jwks
+
+ strategy = _make_strategy()
+ strategy.user_pool_id = user_pool_id
+ strategy.client_id = client_id
+ strategy.region = region
+
+ with patch("requests.get", return_value=mock_response):
+ result = await strategy.validate_token(token)
+
+ assert result.status == AuthStatus.SUCCESS
+ assert result.user_id == "user-cognito-123"
+ assert "operator" in (result.user_roles or [])
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_cognito_validate_token_unknown_kid_returns_invalid():
+ """validate_token returns INVALID when no JWKS entry matches the token's kid."""
+ user_pool_id = "us-east-1_TESTPOOL"
+ client_id = "test-client-id"
+ region = "us-east-1"
+
+ # Generate a valid JWKS+token but serve JWKS with a *different* kid
+ jwks, token = _generate_rs256_jwks_and_token(user_pool_id, client_id, region, kid="actual-kid")
+ # Replace kid in JWKS so the lookup fails
+ jwks["keys"][0]["kid"] = "different-kid"
+
+ mock_response = MagicMock()
+ mock_response.json.return_value = jwks
+
+ strategy = _make_strategy()
+ strategy.user_pool_id = user_pool_id
+ strategy.client_id = client_id
+ strategy.region = region
+
+ with patch("requests.get", return_value=mock_response):
+ result = await strategy.validate_token(token)
+
+ assert result.status == AuthStatus.INVALID
+
+
+# ---------------------------------------------------------------------------
+# _get_public_key — raise_for_status called on JWKS response
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_get_public_key_calls_raise_for_status():
+ """_get_public_key must call raise_for_status() on the JWKS HTTP response.
+
+ A Cognito 403 or 503 returns a non-keys JSON body; without raise_for_status()
+ the kid-lookup silently returns None and every token is rejected with an
+ opaque "Unable to verify token signature" error that is hard to diagnose.
+ """
+ strategy = _make_strategy()
+
+ mock_response = MagicMock()
+ mock_response.json.return_value = {"keys": []}
+
+ with patch("requests.get", return_value=mock_response) as mock_get:
+ await strategy._get_public_key("any-kid")
+
+ mock_response.raise_for_status.assert_called_once()
+ mock_get.assert_called_once_with(strategy.jwks_url, timeout=30)
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_get_public_key_non_2xx_propagates_as_invalid_token():
+ """An HTTP error from the JWKS endpoint propagates and causes INVALID token result.
+
+ The HTTPError raised by raise_for_status() is caught by the outer except clause
+ in _get_public_key, logged, and returns None — which validate_token converts
+ to an INVALID AuthResult rather than swallowing the error silently.
+ """
+ import requests as req
+
+ strategy = _make_strategy()
+
+ mock_response = MagicMock()
+ mock_response.raise_for_status.side_effect = req.exceptions.HTTPError("403 Forbidden")
+
+ with patch(
+ "orb.providers.aws.auth.cognito_strategy.jwt.get_unverified_header",
+ return_value={"kid": "test-kid"},
+ ):
+ with patch("requests.get", return_value=mock_response):
+ result = await strategy.validate_token("some.bearer.token")
+
+ assert result.status == AuthStatus.INVALID
+ assert "Unable to verify token signature" in (result.error_message or "")
+
+
+# ---------------------------------------------------------------------------
+# JWKS caching behaviour
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_jwks_fetch_is_cached():
+ """_get_public_key called twice with the same kid only fetches JWKS once."""
+ strategy = _make_strategy()
+
+ mock_response = MagicMock()
+ mock_response.json.return_value = {"keys": []} # kid not found → returns None both times
+
+ with patch("requests.get", return_value=mock_response) as mock_get:
+ await strategy._get_public_key("some-kid")
+ await strategy._get_public_key("some-kid")
+
+ # The HTTP fetch should happen only once; second call should hit the cache.
+ mock_get.assert_called_once()
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_jwks_cache_evicted_on_invalid_key_type():
+ """Cache entry is evicted when _get_public_key raises InvalidTokenError for a non-RSA key.
+
+ A subsequent call should re-fetch from the endpoint so that a key rotation
+ is picked up without requiring a process restart.
+ """
+ import jwt as pyjwt
+
+ strategy = _make_strategy()
+
+ ec_jwks = {
+ "keys": [
+ {
+ "kid": "ec-kid",
+ "kty": "EC",
+ "crv": "P-256",
+ "x": "abc",
+ "y": "def",
+ }
+ ]
+ }
+
+ mock_response = MagicMock()
+ mock_response.json.return_value = ec_jwks
+
+ with patch("requests.get", return_value=mock_response) as mock_get:
+ # First call: fetches JWKS and raises because key type is not RSA.
+ with pytest.raises(pyjwt.InvalidTokenError):
+ await strategy._get_public_key("ec-kid")
+
+ # Cache entry must have been evicted on the error.
+ assert strategy.jwks_url not in strategy._jwks_cache
+
+ # Second call: must re-fetch (not serve from cache).
+ with pytest.raises(pyjwt.InvalidTokenError):
+ await strategy._get_public_key("ec-kid")
+
+ assert mock_get.call_count == 2
diff --git a/tests/unit/providers/aws/test_handler_merge_integration.py b/tests/providers/aws/unit/test_handler_merge_integration.py
similarity index 100%
rename from tests/unit/providers/aws/test_handler_merge_integration.py
rename to tests/providers/aws/unit/test_handler_merge_integration.py
diff --git a/tests/providers/aws/unit/test_iam_strategy.py b/tests/providers/aws/unit/test_iam_strategy.py
new file mode 100644
index 000000000..c7cc00733
--- /dev/null
+++ b/tests/providers/aws/unit/test_iam_strategy.py
@@ -0,0 +1,285 @@
+"""Unit tests for IAMAuthStrategy."""
+
+from __future__ import annotations
+
+from typing import Any
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from orb.infrastructure.adapters.ports.auth import AuthContext, AuthStatus
+
+
+def _make_context(**kwargs) -> AuthContext:
+ defaults: dict[str, Any] = {
+ "method": "GET",
+ "path": "/api/v1/machines",
+ "headers": {},
+ "query_params": {},
+ }
+ defaults.update(kwargs)
+ return AuthContext(**defaults)
+
+
+def _make_logger() -> MagicMock:
+ logger = MagicMock()
+ logger.debug = MagicMock()
+ logger.info = MagicMock()
+ logger.warning = MagicMock()
+ logger.error = MagicMock()
+ return logger
+
+
+def _make_strategy(assume_permissions: bool = True) -> Any:
+ """Build IAMAuthStrategy with mocked AWS clients."""
+ from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
+
+ logger = _make_logger()
+
+ with patch("orb.providers.aws.auth.iam_strategy.AWSSessionFactory") as mock_factory:
+ mock_session = MagicMock()
+ mock_sts = MagicMock()
+ mock_iam = MagicMock()
+ mock_session.client.side_effect = lambda svc, **kw: mock_sts if svc == "sts" else mock_iam
+ mock_factory.create_session.return_value = mock_session
+
+ strategy = IAMAuthStrategy(
+ logger=logger,
+ region="us-east-1",
+ assume_permissions=assume_permissions,
+ )
+
+ strategy.sts_client = mock_sts
+ strategy.iam_client = mock_iam
+ return strategy
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — happy path
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_authenticate_success_assume_permissions(monkeypatch):
+ """IAM authenticate returns SUCCESS with permissions when assume_permissions=True and dev env var is set."""
+ monkeypatch.setenv("ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY", "true")
+
+ strategy = _make_strategy(assume_permissions=True)
+ strategy.sts_client.get_caller_identity.return_value = {
+ "UserId": "AIDAEXAMPLE",
+ "Account": "123456789012",
+ "Arn": "arn:aws:iam::123456789012:user/testuser",
+ }
+
+ result = await strategy.authenticate(_make_context())
+
+ assert result.status == AuthStatus.SUCCESS
+ assert result.user_id is not None
+ assert len(result.permissions) > 0
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — disabled strategy
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_authenticate_disabled():
+ """IAM authenticate returns FAILED when strategy is disabled."""
+ strategy = _make_strategy()
+ strategy.enabled = False
+
+ result = await strategy.authenticate(_make_context())
+
+ assert result.status == AuthStatus.FAILED
+ assert "disabled" in (result.error_message or "").lower()
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — no credentials
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_authenticate_no_credentials():
+ """IAM authenticate returns FAILED when AWS credentials are absent."""
+ from botocore.exceptions import NoCredentialsError
+
+ strategy = _make_strategy()
+ strategy.sts_client.get_caller_identity.side_effect = NoCredentialsError()
+
+ result = await strategy.authenticate(_make_context())
+
+ assert result.status == AuthStatus.FAILED
+ assert "credentials" in (result.error_message or "").lower()
+
+
+# ---------------------------------------------------------------------------
+# _check_permissions — assume_permissions=False → deny
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_check_permissions_deny_without_assume():
+ """_check_permissions returns empty list when assume_permissions is False."""
+ strategy = _make_strategy(assume_permissions=False)
+ strategy._assume_permissions = False
+
+ permissions = await strategy._check_permissions({"Arn": "arn:aws:iam::123:user/u"})
+
+ assert permissions == []
+
+
+# ---------------------------------------------------------------------------
+# _determine_roles — admin detection
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_determine_roles_admin():
+ """_determine_roles detects admin when ARN contains a known admin pattern."""
+ strategy = _make_strategy()
+
+ roles = await strategy._determine_roles(
+ {"Arn": "arn:aws:iam::123456789012:user/Admin"},
+ ["ec2:DescribeInstances"],
+ )
+
+ assert "admin" in roles
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_determine_roles_service_account():
+ """_determine_roles adds service_account role for role-based ARNs."""
+ strategy = _make_strategy()
+
+ roles = await strategy._determine_roles(
+ {"Arn": "arn:aws:iam::123:role/my-service-role"},
+ [],
+ )
+
+ assert "service_account" in roles
+
+
+# ---------------------------------------------------------------------------
+# from_auth_config classmethod
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_iam_from_auth_config_defaults():
+ """from_auth_config builds strategy using IAMAuthSubConfig defaults."""
+ from orb.config.schemas.server_schema import AuthConfig
+
+ auth_config = AuthConfig(strategy="iam") # type: ignore[call-arg] # pydantic default fields
+
+ with patch("orb.providers.aws.auth.iam_strategy.AWSSessionFactory") as mock_factory:
+ mock_session = MagicMock()
+ mock_session.client.return_value = MagicMock()
+ mock_factory.create_session.return_value = mock_session
+
+ from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
+
+ strategy = IAMAuthStrategy.from_auth_config(auth_config)
+
+ assert strategy.region == "us-east-1"
+ assert strategy._assume_permissions is False
+
+
+# ---------------------------------------------------------------------------
+# assume_permissions=True without env var → silently disabled + CRITICAL logged
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_iam_assume_permissions_without_env_var_is_disabled(monkeypatch):
+ """
+ assume_permissions=True in config is ignored when
+ ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY is not set.
+ A CRITICAL warning must be logged and _assume_permissions must be False.
+ """
+ monkeypatch.delenv("ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY", raising=False)
+
+ from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
+
+ logger = _make_logger()
+
+ with patch("orb.providers.aws.auth.iam_strategy.AWSSessionFactory") as mock_factory:
+ mock_session = MagicMock()
+ mock_session.client.return_value = MagicMock()
+ mock_factory.create_session.return_value = mock_session
+
+ strategy = IAMAuthStrategy(
+ logger=logger,
+ region="us-east-1",
+ assume_permissions=True, # config says True …
+ )
+
+ # … but env var absent → must be disabled
+ assert strategy._assume_permissions is False
+
+ # CRITICAL must have been logged to warn the operator
+ critical_calls = logger.critical.call_args_list
+ assert critical_calls, "Expected at least one logger.critical() call"
+ combined = " ".join(str(c) for c in critical_calls)
+ assert "ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY" in combined
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_iam_assume_permissions_without_env_var_returns_empty_permissions(monkeypatch):
+ """
+ When assume_permissions=True but env var is absent, _check_permissions
+ returns an empty list (deny-all).
+ """
+ monkeypatch.delenv("ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY", raising=False)
+
+ from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
+
+ logger = _make_logger()
+
+ with patch("orb.providers.aws.auth.iam_strategy.AWSSessionFactory") as mock_factory:
+ mock_session = MagicMock()
+ mock_session.client.return_value = MagicMock()
+ mock_factory.create_session.return_value = mock_session
+
+ strategy = IAMAuthStrategy(
+ logger=logger,
+ region="us-east-1",
+ assume_permissions=True,
+ )
+
+ permissions = await strategy._check_permissions({"Arn": "arn:aws:iam::123:user/u"})
+ assert permissions == []
+
+
+@pytest.mark.unit
+def test_iam_assume_permissions_with_env_var_is_active(monkeypatch):
+ """
+ assume_permissions=True is honoured when
+ ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY=true is set.
+ """
+ monkeypatch.setenv("ORB_IAM_ASSUME_PERMISSIONS_DEV_ONLY", "true")
+
+ from orb.providers.aws.auth.iam_strategy import IAMAuthStrategy
+
+ logger = _make_logger()
+
+ with patch("orb.providers.aws.auth.iam_strategy.AWSSessionFactory") as mock_factory:
+ mock_session = MagicMock()
+ mock_session.client.return_value = MagicMock()
+ mock_factory.create_session.return_value = mock_session
+
+ strategy = IAMAuthStrategy(
+ logger=logger,
+ region="us-east-1",
+ assume_permissions=True,
+ )
+
+ assert strategy._assume_permissions is True
diff --git a/tests/unit/providers/aws/test_launch_template_manager.py b/tests/providers/aws/unit/test_launch_template_manager.py
similarity index 99%
rename from tests/unit/providers/aws/test_launch_template_manager.py
rename to tests/providers/aws/unit/test_launch_template_manager.py
index 2b16b09e6..ce986cc6b 100644
--- a/tests/unit/providers/aws/test_launch_template_manager.py
+++ b/tests/providers/aws/unit/test_launch_template_manager.py
@@ -8,6 +8,7 @@
import os
import sys
+from typing import cast
from unittest.mock import Mock
import pytest
@@ -273,7 +274,7 @@ def test_use_existing_template_strategy_describe_failure_warns_and_passes_throug
assert result.template_id == "lt-missing"
assert result.is_new_template is False
assert result.is_new_version is False
- self.manager._logger.warning.assert_called()
+ cast(Mock, self.manager._logger).warning.assert_called()
def test_use_existing_template_strategy_throttling_propagates(self):
"""Transient errors (Throttling) must propagate, not warn-and-pass-through."""
@@ -333,7 +334,7 @@ def test_inspect_lt_networking_unauthorized_returns_unknown(self):
result = self.manager.inspect_launch_template_networking("lt-x", "$Latest")
assert result == LTNetworkingState.UNKNOWN_UNAUTHORIZED
- self.manager._logger.warning.assert_called()
+ cast(Mock, self.manager._logger).warning.assert_called()
def test_inspect_lt_networking_throttling_propagates(self):
"""Transient errors (Throttling) must propagate, not return UNKNOWN_UNAUTHORIZED."""
diff --git a/tests/unit/providers/aws/test_native_spec_package_context.py b/tests/providers/aws/unit/test_native_spec_package_context.py
similarity index 100%
rename from tests/unit/providers/aws/test_native_spec_package_context.py
rename to tests/providers/aws/unit/test_native_spec_package_context.py
diff --git a/tests/unit/providers/aws/test_registration.py b/tests/providers/aws/unit/test_registration.py
similarity index 100%
rename from tests/unit/providers/aws/test_registration.py
rename to tests/providers/aws/unit/test_registration.py
diff --git a/tests/unit/providers/aws/test_spec_file_loading.py b/tests/providers/aws/unit/test_spec_file_loading.py
similarity index 100%
rename from tests/unit/providers/aws/test_spec_file_loading.py
rename to tests/providers/aws/unit/test_spec_file_loading.py
diff --git a/tests/unit/providers/aws/test_tag_specifications_guard.py b/tests/providers/aws/unit/test_tag_specifications_guard.py
similarity index 100%
rename from tests/unit/providers/aws/test_tag_specifications_guard.py
rename to tests/providers/aws/unit/test_tag_specifications_guard.py
diff --git a/tests/providers/aws/unit/utilities/__init__.py b/tests/providers/aws/unit/utilities/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/providers/aws/unit/utilities/ec2/__init__.py b/tests/providers/aws/unit/utilities/ec2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/unit/providers/aws/utilities/ec2/test_instances_derive_cpu_ram.py b/tests/providers/aws/unit/utilities/ec2/test_instances_derive_cpu_ram.py
similarity index 100%
rename from tests/unit/providers/aws/utilities/ec2/test_instances_derive_cpu_ram.py
rename to tests/providers/aws/unit/utilities/ec2/test_instances_derive_cpu_ram.py
diff --git a/tests/providers/contract/base_monitoring_contract.py b/tests/providers/contract/base_monitoring_contract.py
index cc1f5c2b8..d1fcdfebe 100644
--- a/tests/providers/contract/base_monitoring_contract.py
+++ b/tests/providers/contract/base_monitoring_contract.py
@@ -10,6 +10,8 @@
import pytest
+from orb.domain.base.provider_fulfilment import CheckHostsStatusResult
+
class BaseMonitoringContract:
"""Provider-agnostic monitoring contract scenarios."""
@@ -23,10 +25,13 @@ class BaseMonitoringContract:
@pytest.mark.provider_contract
def test_status_returns_list(self, provisioned_resource_ids):
- """check_hosts_status must return a list."""
+ """check_hosts_status must return a CheckHostsStatusResult with an instances list."""
handler, _resource_ids, status_request = provisioned_resource_ids
result = handler.check_hosts_status(status_request)
- assert isinstance(result, list), "check_hosts_status must return a list"
+ assert isinstance(result, CheckHostsStatusResult), (
+ "check_hosts_status must return a CheckHostsStatusResult"
+ )
+ assert isinstance(result.instances, list), "CheckHostsStatusResult.instances must be a list"
@pytest.mark.provider_contract
def test_status_entries_have_required_keys(self, provisioned_resource_ids):
@@ -35,7 +40,7 @@ def test_status_entries_have_required_keys(self, provisioned_resource_ids):
result = handler.check_hosts_status(status_request)
# moto simulators may return empty list for some provider APIs (e.g. ASG)
# — the contract only asserts shape when entries are present
- for entry in result:
+ for entry in result.instances:
assert "instance_id" in entry, f"status entry missing instance_id: {entry}"
assert "status" in entry, f"status entry missing status: {entry}"
@@ -60,7 +65,7 @@ def test_status_after_release_reflects_termination(self, provisioned_resource_id
result = handler.check_hosts_status(status_request)
# Either empty (simulator limitation) or all entries show termination state
terminal_states = {"terminated", "shutting-down", "stopping", "stopped"}
- for entry in result:
+ for entry in result.instances:
assert entry.get("status") in terminal_states, (
f"expected terminal state after release, got: {entry}"
)
diff --git a/tests/unit/application/commands/test_return_request_handler_status.py b/tests/unit/application/commands/test_return_request_handler_status.py
new file mode 100644
index 000000000..97f8ac3d7
--- /dev/null
+++ b/tests/unit/application/commands/test_return_request_handler_status.py
@@ -0,0 +1,207 @@
+"""Regression tests: return-request handler must NOT write COMPLETED on termination accept.
+
+Bug: _execute_deprovisioning_for_request wrote COMPLETED the moment AWS
+TerminateInstances returned success. At that point instances are only
+shutting-down, not yet terminated.
+
+Fix: write IN_PROGRESS ("termination accepted: waiting …") so that the
+background poller can transition to COMPLETED only when all instances reach
+the terminated state.
+"""
+
+from __future__ import annotations
+
+from contextlib import contextmanager
+from unittest.mock import AsyncMock, MagicMock
+
+import pytest
+
+from orb.application.commands.request_creation_handlers import CreateReturnRequestHandler
+from orb.domain.request.request_types import RequestStatus
+
+# ---------------------------------------------------------------------------
+# Minimal fakes
+# ---------------------------------------------------------------------------
+
+
+def _make_uow_factory(machines: list[MagicMock]) -> MagicMock:
+ uow = MagicMock()
+ uow.machines.get_by_id.side_effect = lambda mid: next(
+ (m for m in machines if m.machine_id.value == mid), None
+ )
+ uow.machines.save = MagicMock()
+ uow.requests.get_by_id.return_value = None
+
+ @contextmanager
+ def _create():
+ yield uow
+
+ factory = MagicMock()
+ factory.create_unit_of_work.side_effect = _create
+ return factory
+
+
+def _make_request(request_id: str = "req-001") -> MagicMock:
+ req = MagicMock()
+ req.request_id = request_id
+ req.template_id = "tpl-1"
+ req.provider_name = "aws"
+ req.provider_type = "aws"
+ return req
+
+
+def _make_machine(machine_id: str) -> MagicMock:
+ m = MagicMock()
+ m.machine_id = MagicMock()
+ m.machine_id.value = machine_id
+ m.template_id = "tpl-1"
+ m.request_id = "req-origin-001"
+ m.update_status.return_value = m
+ m.model_copy.return_value = m
+ return m
+
+
+def _make_handler() -> tuple[CreateReturnRequestHandler, MagicMock]:
+ """Return (handler, command_bus_mock)."""
+ logger = MagicMock()
+ container = MagicMock()
+ event_publisher = MagicMock()
+ error_handler = MagicMock()
+ query_bus = MagicMock()
+ query_bus.execute = AsyncMock()
+ provider_selection_port = MagicMock()
+ provider_selection_port.execute_operation = AsyncMock()
+
+ uow_factory = _make_uow_factory([_make_machine("i-aaa"), _make_machine("i-bbb")])
+
+ # Command bus is obtained via container.get(CommandBusPort)
+ command_bus = MagicMock()
+ command_bus.execute = AsyncMock()
+ container.get = MagicMock(return_value=command_bus)
+
+ handler = CreateReturnRequestHandler(
+ uow_factory=uow_factory,
+ logger=logger,
+ container=container,
+ event_publisher=event_publisher,
+ error_handler=error_handler,
+ query_bus=query_bus,
+ provider_selection_port=provider_selection_port,
+ )
+ return handler, command_bus
+
+
+# ---------------------------------------------------------------------------
+# Tests
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+@pytest.mark.application
+class TestReturnRequestHandlerStatus:
+ """Verify that termination-accepted path writes IN_PROGRESS, not COMPLETED."""
+
+ @pytest.mark.asyncio
+ async def test_termination_accepted_writes_in_progress_not_completed(self):
+ """Core regression: handler must write IN_PROGRESS when deprovisioning succeeds."""
+ handler, command_bus = _make_handler()
+ request = _make_request()
+
+ # Patch deprovisioning orchestrator to simulate successful termination accept
+ handler._deprovisioning_orchestrator.execute_deprovisioning = AsyncMock(
+ return_value={"success": True, "successful_operations": 1, "failed_operations": 0}
+ )
+ # Patch machine grouping to return one group, no skipped IDs
+ handler._machine_grouping_service.group_by_resource = MagicMock(
+ return_value=({("aws", "RunInstances", "fleet-1"): [MagicMock()]}, [])
+ )
+
+ await handler._execute_deprovisioning_for_request(["i-aaa", "i-bbb"], request, "aws")
+
+ # Gather all UpdateRequestStatusCommand calls
+ from orb.application.dto.commands import UpdateRequestStatusCommand
+
+ status_calls = [
+ c
+ for c in command_bus.execute.call_args_list
+ if isinstance(c.args[0], UpdateRequestStatusCommand)
+ ]
+
+ statuses_written = [c.args[0].status for c in status_calls]
+
+ # Must NOT contain COMPLETED
+ assert RequestStatus.COMPLETED not in statuses_written, (
+ "Handler wrote COMPLETED immediately on termination accept — bug is not fixed"
+ )
+
+ # Must contain IN_PROGRESS (either from the initial transition or from the
+ # termination-accepted path)
+ assert RequestStatus.IN_PROGRESS in statuses_written, (
+ "Handler did not write IN_PROGRESS after termination was accepted"
+ )
+
+ @pytest.mark.asyncio
+ async def test_termination_accepted_message_is_honest(self):
+ """Status message must not say 'termination initiated' as if it's done."""
+ handler, command_bus = _make_handler()
+ request = _make_request()
+
+ handler._deprovisioning_orchestrator.execute_deprovisioning = AsyncMock(
+ return_value={"success": True, "successful_operations": 1, "failed_operations": 0}
+ )
+ handler._machine_grouping_service.group_by_resource = MagicMock(
+ return_value=({("aws", "RunInstances", "fleet-1"): [MagicMock()]}, [])
+ )
+
+ await handler._execute_deprovisioning_for_request(["i-aaa"], request, "aws")
+
+ from orb.application.dto.commands import UpdateRequestStatusCommand
+
+ status_calls = [
+ c
+ for c in command_bus.execute.call_args_list
+ if isinstance(c.args[0], UpdateRequestStatusCommand)
+ and c.args[0].status == RequestStatus.IN_PROGRESS
+ ]
+ messages = [c.args[0].message for c in status_calls]
+
+ # At least one IN_PROGRESS message must be about "waiting" or "accepted"
+ honest_messages = [
+ m
+ for m in messages
+ if "waiting" in m.lower() or "accepted" in m.lower() or "terminating" in m.lower()
+ ]
+ assert honest_messages, (
+ f"No honest termination-in-progress message found. Messages: {messages}"
+ )
+
+ @pytest.mark.asyncio
+ async def test_deprovisioning_failure_writes_failed_not_completed(self):
+ """On deprovisioning failure, write FAILED, never COMPLETED."""
+ handler, command_bus = _make_handler()
+ request = _make_request()
+
+ handler._deprovisioning_orchestrator.execute_deprovisioning = AsyncMock(
+ return_value={
+ "success": False,
+ "errors": ["TerminateInstances throttled"],
+ "failed_operations": 1,
+ }
+ )
+ handler._machine_grouping_service.group_by_resource = MagicMock(
+ return_value=({("aws", "RunInstances", "fleet-1"): [MagicMock()]}, [])
+ )
+
+ await handler._execute_deprovisioning_for_request(["i-aaa"], request, "aws")
+
+ from orb.application.dto.commands import UpdateRequestStatusCommand
+
+ status_calls = [
+ c
+ for c in command_bus.execute.call_args_list
+ if isinstance(c.args[0], UpdateRequestStatusCommand)
+ ]
+ statuses_written = [c.args[0].status for c in status_calls]
+
+ assert RequestStatus.COMPLETED not in statuses_written
+ assert RequestStatus.FAILED in statuses_written
diff --git a/tests/unit/application/queries/test_get_request_handler.py b/tests/unit/application/queries/test_get_request_handler.py
index 3621f3a92..04b8035ef 100644
--- a/tests/unit/application/queries/test_get_request_handler.py
+++ b/tests/unit/application/queries/test_get_request_handler.py
@@ -146,13 +146,25 @@ async def test_get_request_falls_back_to_stored_state_on_sync_error():
@pytest.mark.asyncio
async def test_get_request_returns_synced_dto_on_success():
- """Normal (no-error) path returns a DTO and writes to cache."""
- request = _make_request(_ID_SUCCESS)
+ """Normal (no-error) path returns a DTO; cache is written when request transitions to terminal.
- handler, mock_query_service, mock_cache_service = _make_handler(request, sync_side_effect=None)
+ Sequence under test:
+ 1. First get_request() call → IN_PROGRESS (not short-circuited, sync runs).
+ 2. Sync drives the status to COMPLETED.
+ 3. Second get_request() call after sync → COMPLETED.
+ 4. request.status.is_terminal() is True → cache_request() is called once.
+ """
+ in_progress_request = _make_request(_ID_SUCCESS)
+ in_progress_request = in_progress_request.update_status(RequestStatus.IN_PROGRESS, "syncing")
+ completed_request = in_progress_request.update_status(RequestStatus.COMPLETED, "done")
- # After sync the handler re-fetches the request; return the same object for simplicity
- mock_query_service.get_request = AsyncMock(return_value=request)
+ handler, mock_query_service, mock_cache_service = _make_handler(
+ in_progress_request, sync_side_effect=None
+ )
+
+ # First call returns IN_PROGRESS so the handler takes the sync path.
+ # Second call (after status update) returns COMPLETED so the cache is written.
+ mock_query_service.get_request = AsyncMock(side_effect=[in_progress_request, completed_request])
query = GetRequestQuery(request_id=_ID_SUCCESS)
result = await handler.execute_query(query)
@@ -160,10 +172,33 @@ async def test_get_request_returns_synced_dto_on_success():
assert isinstance(result, RequestDTO)
assert result.request_id == _ID_SUCCESS
- # Cache SHOULD be written on the success path
+ # Cache SHOULD be written once the request reaches a terminal state
mock_cache_service.cache_request.assert_called_once()
+@pytest.mark.asyncio
+async def test_get_request_does_not_cache_non_terminal():
+ """Non-terminal requests (PENDING, IN_PROGRESS) are never cached.
+
+ Caching non-terminal states prevents the sync loop from observing provider
+ state transitions and keeps the request stuck at the cached status.
+ """
+ pending_request = _make_request(_ID_SUCCESS) # PENDING by default
+
+ handler, mock_query_service, mock_cache_service = _make_handler(
+ pending_request, sync_side_effect=None
+ )
+
+ mock_query_service.get_request = AsyncMock(return_value=pending_request)
+
+ query = GetRequestQuery(request_id=_ID_SUCCESS)
+ result = await handler.execute_query(query)
+
+ assert isinstance(result, RequestDTO)
+ # Non-terminal → cache_request must NOT be called
+ mock_cache_service.cache_request.assert_not_called()
+
+
@pytest.mark.asyncio
async def test_get_request_raises_entity_not_found_when_missing():
"""EntityNotFoundError (request missing) propagates — no swallowing."""
diff --git a/tests/unit/application/queries/test_list_requests_filter_by_type.py b/tests/unit/application/queries/test_list_requests_filter_by_type.py
index 603eaadc0..43f577afe 100644
--- a/tests/unit/application/queries/test_list_requests_filter_by_type.py
+++ b/tests/unit/application/queries/test_list_requests_filter_by_type.py
@@ -82,15 +82,10 @@ def _run_handler_with_requests(all_requests, query):
mock_uow_factory = MagicMock()
mock_uow_factory.create_unit_of_work.return_value = mock_uow
- handler = ListRequestsHandler(
- uow_factory=mock_uow_factory,
- logger=mock_logger,
- error_handler=mock_error_handler,
- generic_filter_service=mock_filter_service,
- )
-
- # Patch RequestDTOFactory so we don't need full domain wiring
- with patch("orb.application.factories.request_dto_factory.RequestDTOFactory") as MockFactory:
+ # Patch RequestDTOFactory at the import site so we don't need full domain wiring.
+ # Patch must wrap handler construction because the handler eagerly instantiates
+ # self._dto_factory = RequestDTOFactory() in __init__.
+ with patch("orb.application.queries.request_query_handlers.RequestDTOFactory") as MockFactory:
mock_dto_factory = MagicMock()
MockFactory.return_value = mock_dto_factory
mock_dto_factory.create_from_domain.side_effect = lambda req, machines: SimpleNamespace(
@@ -98,6 +93,13 @@ def _run_handler_with_requests(all_requests, query):
request_type=req.request_type.value,
)
+ handler = ListRequestsHandler(
+ uow_factory=mock_uow_factory,
+ logger=mock_logger,
+ error_handler=mock_error_handler,
+ generic_filter_service=mock_filter_service,
+ )
+
import asyncio
return asyncio.run(handler.execute_query(query))
diff --git a/tests/unit/application/queries/test_request_status_capacity.py b/tests/unit/application/queries/test_request_status_capacity.py
index 0f1be0225..34774fb3a 100644
--- a/tests/unit/application/queries/test_request_status_capacity.py
+++ b/tests/unit/application/queries/test_request_status_capacity.py
@@ -6,6 +6,7 @@
import pytest
from orb.application.services.request_status_service import RequestStatusService
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
from orb.domain.base.value_objects import InstanceType
from orb.domain.machine.aggregate import Machine
from orb.domain.machine.machine_identifiers import MachineId
@@ -63,8 +64,17 @@ def test_fleet_capacity_completed_even_with_few_instances():
service = _make_service()
request = _request(RequestType.ACQUIRE, RequestStatus.IN_PROGRESS, requested_count=10)
machines = _machines(MachineStatus.RUNNING, MachineStatus.RUNNING)
-
- new_status, _ = service.determine_status_from_machines([], machines, request, {})
+ # 2 running out of 10 requested — provider reports in_progress
+ fulfilment = ProviderFulfilment(
+ state="in_progress",
+ message="2 of 10 running, still provisioning",
+ target_units=10,
+ fulfilled_units=2,
+ running_count=2,
+ )
+ new_status, _ = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
# 2 running out of 10 requested — partial or in-progress, not completed
assert new_status in (
RequestStatus.IN_PROGRESS.value,
@@ -79,8 +89,16 @@ def test_fleet_capacity_in_progress_when_under_target():
request = _request(RequestType.ACQUIRE, RequestStatus.IN_PROGRESS, requested_count=10)
# All pending — running=0, failed=0, so returns IN_PROGRESS
machines = _machines(MachineStatus.PENDING, MachineStatus.PENDING)
-
- new_status, _ = service.determine_status_from_machines([], machines, request, {})
+ fulfilment = ProviderFulfilment(
+ state="in_progress",
+ message="2 of 10 pending",
+ target_units=10,
+ fulfilled_units=0,
+ pending_count=2,
+ )
+ new_status, _ = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.IN_PROGRESS.value
@@ -95,8 +113,17 @@ def test_fleet_capacity_partial_with_failures():
MachineStatus.FAILED,
MachineStatus.FAILED,
)
-
- new_status, _ = service.determine_status_from_machines([], machines, request, {})
+ fulfilment = ProviderFulfilment(
+ state="partial",
+ message="2 running, 3 failed — partial fulfilment",
+ target_units=5,
+ fulfilled_units=2,
+ running_count=2,
+ failed_count=3,
+ )
+ new_status, _ = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.PARTIAL.value
@@ -110,8 +137,16 @@ def test_fleet_capacity_all_failed():
MachineStatus.FAILED,
MachineStatus.FAILED,
)
-
- new_status, _ = service.determine_status_from_machines([], machines, request, {})
+ fulfilment = ProviderFulfilment(
+ state="failed",
+ message="All 4 instances failed",
+ target_units=4,
+ fulfilled_units=0,
+ failed_count=4,
+ )
+ new_status, _ = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.FAILED.value
@@ -120,8 +155,16 @@ def test_asg_capacity_completed():
service = _make_service()
request = _request(RequestType.ACQUIRE, RequestStatus.IN_PROGRESS, requested_count=1)
machines = _machines(MachineStatus.RUNNING)
-
- new_status, _ = service.determine_status_from_machines([], machines, request, {})
+ fulfilment = ProviderFulfilment(
+ state="fulfilled",
+ message="1 of 1 running",
+ target_units=1,
+ fulfilled_units=1,
+ running_count=1,
+ )
+ new_status, _ = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.COMPLETED.value
@@ -131,8 +174,16 @@ def test_asg_capacity_in_progress():
request = _request(RequestType.ACQUIRE, RequestStatus.IN_PROGRESS, requested_count=6)
# All pending — running=0, failed=0, so returns IN_PROGRESS
machines = _machines(MachineStatus.PENDING, MachineStatus.PENDING)
-
- new_status, _ = service.determine_status_from_machines([], machines, request, {})
+ fulfilment = ProviderFulfilment(
+ state="in_progress",
+ message="2 pending, waiting for target of 6",
+ target_units=6,
+ fulfilled_units=0,
+ pending_count=2,
+ )
+ new_status, _ = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.IN_PROGRESS.value
@@ -141,8 +192,16 @@ def test_runinstances_completed_without_capacity_metadata():
service = _make_service()
request = _request(RequestType.ACQUIRE, RequestStatus.IN_PROGRESS, requested_count=2)
machines = _machines(MachineStatus.RUNNING, MachineStatus.RUNNING)
-
- new_status, msg = service.determine_status_from_machines([], machines, request, {})
+ fulfilment = ProviderFulfilment(
+ state="fulfilled",
+ message="2 of 2 running",
+ target_units=2,
+ fulfilled_units=2,
+ running_count=2,
+ )
+ new_status, msg = service.determine_status_from_machines(
+ [], machines, request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.COMPLETED.value
assert msg is not None
assert "running" in msg.lower()
@@ -155,8 +214,16 @@ def test_runinstances_timeout_when_no_instances_after_long_time():
request = _request(
RequestType.ACQUIRE, RequestStatus.IN_PROGRESS, requested_count=1, created_at=old_time
)
-
- new_status, msg = service.determine_status_from_machines([], [], request, {})
+ # No machines yet — provider still reports in_progress (keep polling)
+ fulfilment = ProviderFulfilment(
+ state="in_progress",
+ message="No instances yet after 31 minutes",
+ target_units=1,
+ fulfilled_units=0,
+ )
+ new_status, _msg = service.determine_status_from_machines(
+ [], [], request, {"provider_fulfilment": fulfilment}
+ )
# No machines yet — service returns IN_PROGRESS (keep polling)
assert new_status == RequestStatus.IN_PROGRESS.value
@@ -164,7 +231,8 @@ def test_runinstances_timeout_when_no_instances_after_long_time():
@pytest.mark.unit
def test_return_request_completed_when_all_terminated():
service = _make_service()
- request = _request(RequestType.RETURN, RequestStatus.IN_PROGRESS, requested_count=3)
+ # requested_count matches the number of terminated machines provided
+ request = _request(RequestType.RETURN, RequestStatus.IN_PROGRESS, requested_count=2)
machines = _machines(MachineStatus.TERMINATED, MachineStatus.TERMINATED)
new_status, _ = service.determine_status_from_machines([], machines, request, {})
@@ -193,7 +261,15 @@ def test_provisioning_failure_metadata_forces_failed():
"error_message": "Failed to create EC2 fleet",
},
)
-
+ # Provider still reports in_progress — application polls until provider says failed
+ fulfilment = ProviderFulfilment(
+ state="in_progress",
+ message="Provisioning in progress",
+ target_units=3,
+ fulfilled_units=0,
+ )
# No machines, no provider machines — service returns IN_PROGRESS (keep polling)
- new_status, _ = service.determine_status_from_machines([], [], request, {})
+ new_status, _ = service.determine_status_from_machines(
+ [], [], request, {"provider_fulfilment": fulfilment}
+ )
assert new_status == RequestStatus.IN_PROGRESS.value
diff --git a/tests/unit/application/services/orchestration/test_return_machines_orchestrator.py b/tests/unit/application/services/orchestration/test_return_machines_orchestrator.py
index 404d08dc5..e65c8c5fa 100644
--- a/tests/unit/application/services/orchestration/test_return_machines_orchestrator.py
+++ b/tests/unit/application/services/orchestration/test_return_machines_orchestrator.py
@@ -189,3 +189,50 @@ async def _set_request_ids(cmd):
result = await orchestrator.execute(input)
assert result.status == "pending"
assert result.request_id == "ret-req-001"
+
+ @pytest.mark.asyncio
+ async def test_output_machine_ids_populated_from_explicit_input(
+ self, orchestrator, mock_command_bus
+ ):
+ """machine_ids in output must reflect the machines submitted for return."""
+
+ async def _set_request_ids(cmd):
+ cmd.created_request_ids = ["ret-req-001"]
+
+ mock_command_bus.execute.side_effect = _set_request_ids
+ input = ReturnMachinesInput(machine_ids=["i-aaa", "i-bbb", "i-ccc"])
+ result = await orchestrator.execute(input)
+ assert isinstance(result, ReturnMachinesOutput)
+ assert result.machine_ids == ["i-aaa", "i-bbb", "i-ccc"]
+
+ @pytest.mark.asyncio
+ async def test_output_machine_ids_populated_from_all_machines_path(
+ self, orchestrator, mock_query_bus, mock_command_bus
+ ):
+ """machine_ids in output must reflect resolved IDs from --all path."""
+ mock_query_bus.execute.return_value = [
+ MagicMock(machine_id="i-001"),
+ MagicMock(machine_id="i-002"),
+ ]
+
+ async def _set_request_ids(cmd):
+ cmd.created_request_ids = ["ret-req-001"]
+
+ mock_command_bus.execute.side_effect = _set_request_ids
+ input = ReturnMachinesInput(all_machines=True)
+ result = await orchestrator.execute(input)
+ assert sorted(result.machine_ids) == ["i-001", "i-002"]
+
+ @pytest.mark.asyncio
+ async def test_output_machine_ids_empty_when_no_op(self, orchestrator, mock_command_bus):
+ """machine_ids must be empty (default) when no request was created."""
+
+ async def _set_empty(cmd):
+ cmd.created_request_ids = []
+ cmd.skipped_machines = ["i-001"]
+
+ mock_command_bus.execute.side_effect = _set_empty
+ input = ReturnMachinesInput(machine_ids=["i-001"])
+ result = await orchestrator.execute(input)
+ assert result.status == "no_op"
+ assert result.machine_ids == []
diff --git a/tests/unit/application/services/test_machine_sync_service.py b/tests/unit/application/services/test_machine_sync_service.py
new file mode 100644
index 000000000..c067e4902
--- /dev/null
+++ b/tests/unit/application/services/test_machine_sync_service.py
@@ -0,0 +1,145 @@
+"""Unit tests for MachineSyncService — basic behaviour and OperationOutcome awareness.
+
+The sync service does not directly call acquire/return_machines/get_status; it uses
+execute_operation via ProviderRegistryService. These tests cover the core paths
+that interact with the outcome-aware request status logic.
+"""
+
+from unittest.mock import MagicMock
+
+import pytest
+
+from orb.application.services.machine_sync_service import MachineSyncService
+from orb.domain.machine.machine_status import MachineStatus
+
+
+def _make_service() -> MachineSyncService:
+ command_bus = MagicMock()
+ uow_factory = MagicMock()
+ config_port = MagicMock()
+ logger = MagicMock()
+ return MachineSyncService(
+ command_bus=command_bus,
+ uow_factory=uow_factory,
+ config_port=config_port,
+ logger=logger,
+ )
+
+
+def _make_request(request_type: str = "acquire", provider_api: str = "RunInstances"):
+ req = MagicMock()
+ req.request_id = MagicMock()
+ req.request_id.__str__ = lambda self: "req-sync-test"
+ req.request_type.value = request_type
+ req.provider_name = "aws_default_us-east-1"
+ req.provider_api = provider_api
+ req.template_id = "tmpl-1"
+ req.resource_ids = ["fleet-1"]
+ req.machine_ids = []
+ req.metadata = {}
+ return req
+
+
+def _make_machine(mid: str, status: MachineStatus = MachineStatus.RUNNING):
+ m = MagicMock()
+ m.machine_id.value = mid
+ m.status = status
+ return m
+
+
+@pytest.mark.unit
+class TestFetchProviderMachinesNoRegistryService:
+ """fetch_provider_machines returns (db_machines, {}) when no registry service."""
+
+ @pytest.mark.asyncio
+ async def test_returns_db_machines_when_registry_service_missing(self):
+ svc = _make_service()
+ db_machines = [_make_machine("i-1")]
+ req = _make_request()
+
+ # No provider_registry_service injected → should raise RuntimeError
+ # which is caught internally and returns (db_machines, {})
+ machines, meta = await svc.fetch_provider_machines(req, db_machines) # type: ignore[arg-type]
+ assert machines == db_machines
+ assert meta == {}
+
+
+@pytest.mark.unit
+class TestFetchProviderMachinesEmpty:
+ """fetch_provider_machines returns ([], {}) when no machine context."""
+
+ @pytest.mark.asyncio
+ async def test_no_resource_ids_and_no_db_machines_returns_empty(self):
+ svc = _make_service()
+ req = _make_request()
+ req.resource_ids = []
+ req.machine_ids = []
+
+ machines, meta = await svc.fetch_provider_machines(req, [])
+ assert machines == []
+ assert meta == {}
+
+
+@pytest.mark.unit
+class TestFetchProviderMachinesWithMockRegistry:
+ """fetch_provider_machines propagates provider instance data."""
+
+ @pytest.mark.asyncio
+ async def test_acquire_path_calls_registry_service(self):
+ """fetch_provider_machines calls the registry service for acquire requests."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service()
+
+ captured: list = []
+
+ async def _capture(provider_name, operation):
+ captured.append(operation.operation_type.value)
+ return ProviderResult.success_result(data={"instances": []})
+
+ registry_svc = MagicMock()
+ registry_svc.execute_operation = _capture
+ svc._provider_registry_service = registry_svc
+
+ req = _make_request(request_type="acquire")
+ db_machines: list = []
+
+ machines, _meta = await svc.fetch_provider_machines(req, db_machines)
+ # The registry was called and returned empty instances → machines is empty
+ assert "describe_resource_instances" in captured
+ assert machines == []
+
+ @pytest.mark.asyncio
+ async def test_return_path_uses_instance_status_operation(self):
+ """For return requests with machine_ids, the GET_INSTANCE_STATUS operation is used."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service()
+
+ called_operations: list = []
+
+ async def _capture(provider_name, operation):
+ called_operations.append(operation.operation_type.value)
+ return ProviderResult.success_result(
+ data={
+ "instances": [
+ {
+ "instance_id": "i-1",
+ "status": "shutting-down",
+ "instance_type": "m5.large",
+ }
+ ]
+ },
+ )
+
+ registry_svc = MagicMock()
+ registry_svc.execute_operation = _capture
+ svc._provider_registry_service = registry_svc
+
+ req = _make_request(request_type="return")
+ req.machine_ids = ["i-1"]
+
+ db_machines = [_make_machine("i-1", MachineStatus.RUNNING)]
+ await svc.fetch_provider_machines(req, db_machines) # type: ignore[arg-type]
+
+ assert "get_instance_status" in called_operations
diff --git a/tests/unit/application/services/test_provisioning_async_guards.py b/tests/unit/application/services/test_provisioning_async_guards.py
new file mode 100644
index 000000000..db3256fa1
--- /dev/null
+++ b/tests/unit/application/services/test_provisioning_async_guards.py
@@ -0,0 +1,340 @@
+"""Tests for async correctness guards in ProvisioningOrchestrationService.
+
+Covers:
+- asyncio.timeout wrapping the execute_operation call in _dispatch_single_attempt
+- asyncio.to_thread offloading _persist_acquiring to a worker thread
+"""
+
+import asyncio
+from typing import cast
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+
+from orb.application.services.provisioning_orchestration_service import (
+ ProvisioningOrchestrationService,
+)
+from orb.domain.base.results import ProviderSelectionResult
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _make_service(dispatch_timeout: float = 10.0) -> ProvisioningOrchestrationService:
+ """Build a service with all mocks wired; dispatch_timeout_seconds from config."""
+ container = MagicMock()
+ logger = MagicMock()
+ provider_selection_port = MagicMock()
+ provider_config_port = MagicMock()
+ config_port = MagicMock()
+ circuit_breaker_factory = MagicMock()
+
+ config_port.get_request_config.return_value = {
+ "dispatch_timeout_seconds": dispatch_timeout,
+ }
+
+ cb = MagicMock()
+ cb.has_state.return_value = False
+ circuit_breaker_factory.return_value = cb
+
+ return ProvisioningOrchestrationService(
+ container=container,
+ logger=logger,
+ provider_selection_port=provider_selection_port,
+ provider_config_port=provider_config_port,
+ config_port=config_port,
+ circuit_breaker_factory=circuit_breaker_factory,
+ )
+
+
+def _make_request(count: int = 1):
+ request = MagicMock()
+ request.request_id = "req-timeout-test"
+ request.requested_count = count
+ request.metadata = {}
+ request.update_metadata = lambda d: request
+ return request
+
+
+def _make_template():
+ template = MagicMock()
+ template.template_id = "tmpl-timeout"
+ return template
+
+
+def _make_selection_result(provider_name: str = "aws_default_us-east-1") -> ProviderSelectionResult:
+ return ProviderSelectionResult(
+ provider_name=provider_name,
+ provider_type="aws",
+ selection_reason="test",
+ confidence=1.0,
+ )
+
+
+# ---------------------------------------------------------------------------
+# asyncio.timeout tests
+# ---------------------------------------------------------------------------
+
+
+class TestDispatchTimeout:
+ """_dispatch_single_attempt must time out when the operation hangs."""
+
+ @pytest.mark.asyncio
+ async def test_timeout_returns_failure_result(self):
+ """When execute_operation hangs beyond the timeout, a failed ProvisioningResult is returned."""
+ svc = _make_service(dispatch_timeout=0.05) # 50 ms — fast for tests
+
+ # Simulate a hung provider: never resolves
+ async def _hang(*_args, **_kwargs):
+ await asyncio.sleep(60) # much longer than the 50 ms timeout
+
+ svc._provider_selection_port.execute_operation = _hang
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ cast(MagicMock, svc._container).get.return_value = scheduler
+
+ result = await svc._dispatch_single_attempt(
+ _make_template(),
+ _make_request(),
+ _make_selection_result(),
+ count=1,
+ dispatch_timeout_seconds=0.05,
+ )
+
+ assert result.success is False
+ assert result.is_final is True
+ assert "timed out" in (result.error_message or "").lower()
+
+ @pytest.mark.asyncio
+ async def test_timeout_logs_warning(self):
+ """A warning is logged when the dispatch times out."""
+ svc = _make_service(dispatch_timeout=0.05)
+
+ async def _hang(*_args, **_kwargs):
+ await asyncio.sleep(60)
+
+ svc._provider_selection_port.execute_operation = _hang
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ cast(MagicMock, svc._container).get.return_value = scheduler
+
+ await svc._dispatch_single_attempt(
+ _make_template(),
+ _make_request(),
+ _make_selection_result(),
+ count=1,
+ dispatch_timeout_seconds=0.05,
+ )
+
+ cast(MagicMock, svc._logger).warning.assert_called()
+ warning_call_args = str(cast(MagicMock, svc._logger).warning.call_args_list)
+ assert "timed out" in warning_call_args.lower() or "timeout" in warning_call_args.lower()
+
+ @pytest.mark.asyncio
+ async def test_fast_operation_completes_normally(self):
+ """An operation that completes within the timeout is not interrupted."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service(dispatch_timeout=10.0)
+
+ provider_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["i-ok"],
+ "instances": [{"id": "i-ok"}],
+ "instance_ids": ["i-ok"],
+ },
+ metadata={},
+ )
+ svc._provider_selection_port.execute_operation = AsyncMock(return_value=provider_result)
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ cast(MagicMock, svc._container).get.return_value = scheduler
+
+ result = await svc._dispatch_single_attempt(
+ _make_template(),
+ _make_request(),
+ _make_selection_result(),
+ count=1,
+ dispatch_timeout_seconds=10.0,
+ )
+
+ assert result.success is True
+ assert result.resource_ids == ["i-ok"]
+
+ @pytest.mark.asyncio
+ async def test_timeout_fires_via_execute_provisioning_config(self):
+ """dispatch_timeout_seconds from config flows through to the dispatch call."""
+ svc = _make_service(dispatch_timeout=0.05)
+
+ call_count = 0
+
+ async def _hang(*_args, **_kwargs):
+ nonlocal call_count
+ call_count += 1
+ await asyncio.sleep(60)
+
+ svc._provider_selection_port.execute_operation = _hang
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ cast(MagicMock, svc._container).get.return_value = scheduler
+
+ request = _make_request(count=1)
+ # Make update_metadata return a fresh mock that also has metadata={} and matching attrs
+ updated_req = MagicMock()
+ updated_req.request_id = "req-timeout-test"
+ updated_req.requested_count = 1
+ updated_req.metadata = {}
+ updated_req.update_metadata = lambda d: updated_req
+ request.update_metadata = lambda d: updated_req
+
+ result = await svc.execute_provisioning(_make_template(), request, _make_selection_result())
+
+ # At least one attempt was made and resulted in failure due to timeout
+ assert call_count >= 1
+ assert result.success is False
+
+
+# ---------------------------------------------------------------------------
+# asyncio.to_thread tests
+# ---------------------------------------------------------------------------
+
+
+class TestPersistAcquiringToThread:
+ """_persist_acquiring must run in a worker thread via asyncio.to_thread."""
+
+ @pytest.mark.asyncio
+ async def test_persist_acquiring_called_via_to_thread(self):
+ """asyncio.to_thread is used when calling _persist_acquiring."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service(dispatch_timeout=10.0)
+
+ # First attempt: partial (is_final=False) — triggers persist_acquiring
+ # Second attempt: fully fulfilled
+ first_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["i-partial"],
+ "instances": [{"id": "i-partial"}],
+ "instance_ids": ["i-partial"],
+ },
+ metadata={},
+ )
+ second_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["i-rest"],
+ "instances": [{"id": "i-rest"}],
+ "instance_ids": ["i-rest"],
+ },
+ metadata={},
+ )
+ svc._provider_selection_port.execute_operation = AsyncMock(
+ side_effect=[first_result, second_result]
+ )
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ cast(MagicMock, svc._container).get.return_value = scheduler
+
+ to_thread_calls: list = []
+
+ original_to_thread = asyncio.to_thread
+
+ async def _spy_to_thread(func, *args, **kwargs):
+ to_thread_calls.append(func)
+ return await original_to_thread(func, *args, **kwargs)
+
+ # Build a request that needs 2 instances so a second attempt is triggered
+ request = MagicMock()
+ request.request_id = "req-persist-test"
+ request.requested_count = 2
+ request.metadata = {}
+
+ call_counter = [0]
+
+ def _update_metadata(d):
+ call_counter[0] += 1
+ return request
+
+ request.update_metadata = _update_metadata
+
+ # _persist_acquiring needs update_status
+ request.update_status = MagicMock(return_value=request)
+
+ # Patch asyncio.to_thread in the module under test
+ with patch(
+ "orb.application.services.provisioning_orchestration_service.asyncio.to_thread",
+ side_effect=_spy_to_thread,
+ ):
+ # We don't need the full loop to run — just verify to_thread is called
+ # by exercising the partial-fulfillment path
+ await svc.execute_provisioning(_make_template(), request, _make_selection_result())
+
+ # _persist_acquiring should have been scheduled via to_thread
+ persist_calls = [f for f in to_thread_calls if f == svc._persist_acquiring]
+ assert len(persist_calls) >= 1, (
+ "_persist_acquiring was not dispatched via asyncio.to_thread"
+ )
+
+ @pytest.mark.asyncio
+ async def test_persist_acquiring_failure_does_not_abort_loop(self):
+ """If _persist_acquiring raises, the retry loop continues with in-memory state."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service(dispatch_timeout=10.0)
+
+ # Two partial attempts, then a final one
+ partial_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["i-p"],
+ "instances": [{"id": "i-p"}],
+ "instance_ids": ["i-p"],
+ },
+ metadata={},
+ )
+ final_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["i-f"],
+ "instances": [{"id": "i-f"}],
+ "instance_ids": ["i-f"],
+ },
+ metadata={},
+ )
+ svc._provider_selection_port.execute_operation = AsyncMock(
+ side_effect=[partial_result, final_result]
+ )
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ cast(MagicMock, svc._container).get.return_value = scheduler
+
+ request = MagicMock()
+ request.request_id = "req-persist-fail"
+ request.requested_count = 2
+ request.metadata = {}
+ request.update_metadata = lambda d: request
+ request.update_status = MagicMock(return_value=request)
+
+ # Make _persist_acquiring return failure (second return value = False)
+ def _failing_persist(req):
+ return req, False
+
+ svc._persist_acquiring = _failing_persist # type: ignore[method-assign]
+
+ async def _inline_to_thread(func, *args, **kwargs):
+ return func(*args, **kwargs)
+
+ with patch(
+ "orb.application.services.provisioning_orchestration_service.asyncio.to_thread",
+ side_effect=_inline_to_thread,
+ ):
+ await svc.execute_provisioning(_make_template(), request, _make_selection_result())
+
+ # Loop should have continued despite persist failure
+ cast(MagicMock, svc._logger).warning.assert_called()
+ warning_msgs = " ".join(str(c) for c in cast(MagicMock, svc._logger).warning.call_args_list)
+ assert "ACQUIRING persist failed" in warning_msgs or "persist" in warning_msgs.lower()
diff --git a/tests/unit/application/services/test_provisioning_orchestration_service.py b/tests/unit/application/services/test_provisioning_orchestration_service.py
new file mode 100644
index 000000000..ebd30f2d2
--- /dev/null
+++ b/tests/unit/application/services/test_provisioning_orchestration_service.py
@@ -0,0 +1,317 @@
+"""Unit tests for ProvisioningOrchestrationService — OperationOutcome integration.
+
+Covers:
+- ProvisioningResult.outcome is set for all return paths
+- ProvisioningResult.is_final is correctly derived from outcome via __post_init__
+- assert_never exhaustiveness compiles (no missed union branches)
+"""
+
+from typing import assert_never
+from unittest.mock import AsyncMock, MagicMock
+
+import pytest
+
+from orb.application.services.provisioning_orchestration_service import (
+ ProvisioningOrchestrationService,
+ ProvisioningResult,
+)
+from orb.domain.base.operation_outcome import (
+ Accepted,
+ Completed,
+ Failed,
+ OperationOutcome,
+ RequiresFollowUp,
+)
+from orb.domain.base.results import ProviderSelectionResult
+
+# ---------------------------------------------------------------------------
+# ProvisioningResult — outcome/is_final derivation
+# ---------------------------------------------------------------------------
+
+
+class TestProvisioningResultOutcomeDerivation:
+ """is_final must be derived from outcome when outcome is present."""
+
+ def test_accepted_sets_is_final_false(self):
+ result = ProvisioningResult(
+ success=True,
+ resource_ids=["r-1"],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ outcome=Accepted(request_id="req-1", pending_resource_ids=["r-1"]),
+ )
+ assert result.is_final is False
+
+ def test_completed_sets_is_final_true(self):
+ result = ProvisioningResult(
+ success=True,
+ resource_ids=["r-1"],
+ machine_ids=["i-1"],
+ instances=[{"id": "i-1"}],
+ provider_data={},
+ outcome=Completed(resource_ids=["r-1"]),
+ )
+ assert result.is_final is True
+
+ def test_failed_sets_is_final_true(self):
+ result = ProvisioningResult(
+ success=False,
+ resource_ids=[],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ outcome=Failed(error="something went wrong"),
+ )
+ assert result.is_final is True
+
+ def test_recoverable_failed_sets_is_final_true(self):
+ result = ProvisioningResult(
+ success=False,
+ resource_ids=[],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ outcome=Failed(error="timeout", recoverable=True),
+ )
+ assert result.is_final is True
+
+ def test_requires_follow_up_sets_is_final_false(self):
+ from orb.application.services.request_follow_up_context import (
+ TerminationFollowUpContext,
+ )
+
+ ctx = TerminationFollowUpContext(pending_instance_ids=["i-1"])
+ result = ProvisioningResult(
+ success=True,
+ resource_ids=[],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ outcome=RequiresFollowUp(context=ctx),
+ )
+ assert result.is_final is False
+
+ def test_no_outcome_honours_explicit_is_final_true(self):
+ """Legacy path: when outcome is None, explicit is_final is preserved."""
+ result = ProvisioningResult(
+ success=True,
+ resource_ids=[],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ is_final=True,
+ )
+ assert result.is_final is True
+
+ def test_no_outcome_honours_explicit_is_final_false(self):
+ """Legacy path: when outcome is None, explicit is_final=False is preserved."""
+ result = ProvisioningResult(
+ success=True,
+ resource_ids=[],
+ machine_ids=[],
+ instances=[],
+ provider_data={},
+ is_final=False,
+ )
+ assert result.is_final is False
+
+
+# ---------------------------------------------------------------------------
+# OperationOutcome exhaustiveness — assert_never
+# ---------------------------------------------------------------------------
+
+
+class TestOperationOutcomeExhaustiveness:
+ """assert_never must cover all union branches without static errors."""
+
+ def test_all_union_branches_handled(self):
+ """Exhaustive match over all OperationOutcome variants must not raise."""
+ outcomes: list[OperationOutcome] = [
+ Accepted(request_id="req-1"),
+ Completed(resource_ids=["r-1"]),
+ Failed(error="oops"),
+ ]
+ results = []
+ for outcome in outcomes:
+ match outcome:
+ case Accepted(request_id=rid):
+ results.append(f"accepted:{rid}")
+ case Completed(resource_ids=ids):
+ results.append(f"completed:{ids}")
+ case RequiresFollowUp():
+ results.append("follow_up")
+ case Failed(error=msg):
+ results.append(f"failed:{msg}")
+ case _ as unreachable:
+ assert_never(unreachable)
+
+ assert results == ["accepted:req-1", "completed:['r-1']", "failed:oops"]
+
+
+# ---------------------------------------------------------------------------
+# _dispatch_single_attempt — outcome attached on success path
+# ---------------------------------------------------------------------------
+
+
+def _make_service() -> ProvisioningOrchestrationService:
+ container = MagicMock()
+ logger = MagicMock()
+ provider_selection_port = MagicMock()
+ provider_config_port = MagicMock()
+ config_port = MagicMock()
+ circuit_breaker_factory = MagicMock()
+
+ config_port.get_request_config.return_value = {
+ "dispatch_timeout_seconds": 10.0,
+ }
+
+ cb = MagicMock()
+ cb.has_state.return_value = False
+ circuit_breaker_factory.return_value = cb
+
+ return ProvisioningOrchestrationService(
+ container=container,
+ logger=logger,
+ provider_selection_port=provider_selection_port,
+ provider_config_port=provider_config_port,
+ config_port=config_port,
+ circuit_breaker_factory=circuit_breaker_factory,
+ )
+
+
+def _make_request(count: int = 2):
+ req = MagicMock()
+ req.request_id = "req-outcome-test"
+ req.requested_count = count
+ req.metadata = {}
+ req.update_metadata = lambda d: req
+ return req
+
+
+def _make_selection_result() -> ProviderSelectionResult:
+ return ProviderSelectionResult(
+ provider_name="aws_default_us-east-1",
+ provider_type="aws",
+ selection_reason="test",
+ confidence=1.0,
+ )
+
+
+class TestDispatchSingleAttemptOutcome:
+ """_dispatch_single_attempt must attach an OperationOutcome to the result."""
+
+ @pytest.mark.asyncio
+ async def test_partial_fulfillment_yields_accepted_outcome(self):
+ """Fewer instances than requested → Accepted (still processing)."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service()
+ provider_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["fleet-1"],
+ "instances": [{"id": "i-1"}], # only 1 of 2 requested
+ "instance_ids": ["i-1"],
+ },
+ metadata={},
+ )
+ svc._provider_selection_port.execute_operation = AsyncMock(return_value=provider_result)
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ svc._container.get.return_value = scheduler
+
+ result = await svc._dispatch_single_attempt(
+ MagicMock(template_id="t-1"),
+ _make_request(count=2),
+ _make_selection_result(),
+ count=2,
+ )
+
+ assert result.success is True
+ assert isinstance(result.outcome, Accepted)
+ assert result.is_final is False
+
+ @pytest.mark.asyncio
+ async def test_full_fulfillment_yields_completed_outcome(self):
+ """All instances returned → Completed."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service()
+ provider_result = ProviderResult.success_result(
+ data={
+ "resource_ids": ["fleet-1"],
+ "instances": [{"id": "i-1"}, {"id": "i-2"}], # 2 of 2
+ "instance_ids": ["i-1", "i-2"],
+ },
+ metadata={},
+ )
+ svc._provider_selection_port.execute_operation = AsyncMock(return_value=provider_result)
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ svc._container.get.return_value = scheduler
+
+ result = await svc._dispatch_single_attempt(
+ MagicMock(template_id="t-1"),
+ _make_request(count=2),
+ _make_selection_result(),
+ count=2,
+ )
+
+ assert result.success is True
+ assert isinstance(result.outcome, Completed)
+ assert result.is_final is True
+
+ @pytest.mark.asyncio
+ async def test_provider_failure_yields_failed_outcome(self):
+ """Provider-side error → Failed outcome attached."""
+ from orb.providers.base.strategy.provider_strategy import ProviderResult
+
+ svc = _make_service()
+ provider_result = ProviderResult.error_result("InsufficientCapacity", "CAPACITY_ERROR")
+ svc._provider_selection_port.execute_operation = AsyncMock(return_value=provider_result)
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ svc._container.get.return_value = scheduler
+
+ result = await svc._dispatch_single_attempt(
+ MagicMock(template_id="t-1"),
+ _make_request(count=2),
+ _make_selection_result(),
+ count=2,
+ )
+
+ assert result.success is False
+ assert isinstance(result.outcome, Failed)
+ assert result.is_final is True
+
+ @pytest.mark.asyncio
+ async def test_timeout_yields_recoverable_failed_outcome(self):
+ """Dispatch timeout → Failed(recoverable=True)."""
+ import asyncio
+
+ svc = _make_service()
+
+ async def _hang(*_a, **_kw):
+ await asyncio.sleep(60)
+
+ svc._provider_selection_port.execute_operation = _hang
+
+ scheduler = MagicMock()
+ scheduler.format_template_for_provider.return_value = {}
+ svc._container.get.return_value = scheduler
+
+ result = await svc._dispatch_single_attempt(
+ MagicMock(template_id="t-1"),
+ _make_request(count=2),
+ _make_selection_result(),
+ count=2,
+ dispatch_timeout_seconds=0.05,
+ )
+
+ assert result.success is False
+ assert isinstance(result.outcome, Failed)
+ assert result.outcome.recoverable is True
+ assert result.is_final is True
diff --git a/tests/unit/application/services/test_provisioning_telemetry_flow.py b/tests/unit/application/services/test_provisioning_telemetry_flow.py
index 2f0d74de2..f20f9214d 100644
--- a/tests/unit/application/services/test_provisioning_telemetry_flow.py
+++ b/tests/unit/application/services/test_provisioning_telemetry_flow.py
@@ -4,6 +4,7 @@
strategy returns routing_info → app layer stamps → ProvisioningResult.provider_data has the fields
"""
+from typing import cast
from unittest.mock import AsyncMock, MagicMock
import pytest
@@ -86,7 +87,7 @@ async def test_routing_info_from_aws_strategy_reaches_provider_data():
# Patch scheduler
scheduler = MagicMock()
scheduler.format_template_for_provider.return_value = {}
- svc._container.get.return_value = scheduler
+ cast(MagicMock, svc._container).get.return_value = scheduler
result = await svc._dispatch_single_attempt(
_make_template(), _make_request(), _make_selection_result(), 1
@@ -122,7 +123,7 @@ async def test_routing_info_from_fallback_strategy_reaches_provider_data():
scheduler = MagicMock()
scheduler.format_template_for_provider.return_value = {}
- svc._container.get.return_value = scheduler
+ cast(MagicMock, svc._container).get.return_value = scheduler
result = await svc._dispatch_single_attempt(
_make_template(), _make_request(), _make_selection_result(), 1
@@ -162,7 +163,7 @@ async def test_routing_info_from_composite_strategy_reaches_provider_data():
scheduler = MagicMock()
scheduler.format_template_for_provider.return_value = {}
- svc._container.get.return_value = scheduler
+ cast(MagicMock, svc._container).get.return_value = scheduler
result = await svc._dispatch_single_attempt(
_make_template(), _make_request(), _make_selection_result(), 1
@@ -193,7 +194,7 @@ async def test_no_routing_info_does_not_break_provider_data():
scheduler = MagicMock()
scheduler.format_template_for_provider.return_value = {}
- svc._container.get.return_value = scheduler
+ cast(MagicMock, svc._container).get.return_value = scheduler
result = await svc._dispatch_single_attempt(
_make_template(), _make_request(), _make_selection_result(), 1
diff --git a/tests/unit/application/services/test_request_status_management_service.py b/tests/unit/application/services/test_request_status_management_service.py
index 0a0f917f6..5140de960 100644
--- a/tests/unit/application/services/test_request_status_management_service.py
+++ b/tests/unit/application/services/test_request_status_management_service.py
@@ -98,6 +98,19 @@ def test_zero_instances_sets_in_progress(self):
call_args = req.update_status.call_args[0]
assert call_args[0] == RequestStatus.IN_PROGRESS
+ def test_zero_instances_no_resource_ids_sets_failed(self):
+ req = _make_request(requested_count=3)
+ req.resource_ids = []
+ self.svc._update_request_status(
+ request=req,
+ instance_count=0,
+ requested_count=3,
+ has_api_errors=False,
+ provider_errors=[],
+ )
+ call_args = req.update_status.call_args[0]
+ assert call_args[0] == RequestStatus.FAILED
+
def test_error_summary_included_in_message(self):
req = _make_request(requested_count=2)
errors = [{"error_code": "InsufficientCapacity", "error_message": "No capacity"}]
diff --git a/tests/unit/application/services/test_request_status_service.py b/tests/unit/application/services/test_request_status_service.py
index 930b719ae..9d27281f5 100644
--- a/tests/unit/application/services/test_request_status_service.py
+++ b/tests/unit/application/services/test_request_status_service.py
@@ -1,8 +1,16 @@
-"""Unit tests for RequestStatusService.determine_status_from_machines — return request logic."""
+"""Unit tests for RequestStatusService.determine_status_from_machines.
+
+The acquire path now trusts ProviderFulfilment exclusively.
+The return path continues to use machine-state counting.
+"""
from unittest.mock import MagicMock
+import pytest
+
from orb.application.services.request_status_service import RequestStatusService
+from orb.domain.base.exceptions import ProviderContractError
+from orb.domain.base.provider_fulfilment import ProviderFulfilment
from orb.domain.machine.machine_status import MachineStatus
from orb.domain.request.request_types import RequestStatus
@@ -11,10 +19,11 @@ def _make_service():
return RequestStatusService(uow_factory=MagicMock(), logger=MagicMock())
-def _make_request(request_type="return"):
+def _make_request(request_type="return", requested_count=2):
req = MagicMock()
req.request_type.value = request_type
- req.requested_count = 2
+ req.requested_count = requested_count
+ req.provider_name = "aws-test"
return req
@@ -24,6 +33,127 @@ def _make_machine(status: MachineStatus):
return m
+def _fulfilment(state, message="test", **kwargs) -> dict:
+ """Return metadata dict with a ProviderFulfilment as the acquire path expects."""
+ return {"provider_fulfilment": ProviderFulfilment(state=state, message=message, **kwargs)}
+
+
+# ---------------------------------------------------------------------------
+# Acquire path — ProviderFulfilment state map
+# ---------------------------------------------------------------------------
+
+
+class TestAcquireFulfilmentStatemap:
+ """Acquire path: each ProviderFulfilment state maps to the right RequestStatus."""
+
+ def setup_method(self):
+ self.svc = _make_service()
+
+ def test_fulfilled_maps_to_completed(self):
+ req = _make_request("acquire", requested_count=4)
+ machines = [_make_machine(MachineStatus.RUNNING)] * 2
+ status, msg = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata=_fulfilment(
+ "fulfilled",
+ "Fleet fulfilled",
+ target_units=4,
+ fulfilled_units=4,
+ running_count=2,
+ pending_count=0,
+ failed_count=0,
+ ),
+ )
+ assert status == RequestStatus.COMPLETED.value
+ assert msg == "Fleet fulfilled"
+
+ def test_in_progress_maps_to_in_progress(self):
+ req = _make_request("acquire", requested_count=4)
+ machines = [_make_machine(MachineStatus.PENDING)] * 4
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata=_fulfilment("in_progress", "waiting"),
+ )
+ assert status == RequestStatus.IN_PROGRESS.value
+
+ def test_partial_maps_to_partial(self):
+ req = _make_request("acquire", requested_count=4)
+ machines = [_make_machine(MachineStatus.RUNNING)] * 2
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata=_fulfilment("partial", "only 2 of 4"),
+ )
+ assert status == RequestStatus.PARTIAL.value
+
+ def test_failed_maps_to_failed(self):
+ req = _make_request("acquire", requested_count=4)
+ machines = [_make_machine(MachineStatus.RUNNING)] * 0
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata=_fulfilment("failed", "all failed"),
+ )
+ assert status == RequestStatus.FAILED.value
+
+
+class TestAcquireMissingFulfilmentRaisesContractError:
+ """Missing ProviderFulfilment raises ProviderContractError — no silent fallback."""
+
+ def setup_method(self):
+ self.svc = _make_service()
+
+ def test_raises_contract_error_when_fulfilment_absent(self):
+ req = _make_request("acquire", requested_count=2)
+ machines = [_make_machine(MachineStatus.RUNNING)] * 2
+ with pytest.raises(ProviderContractError):
+ self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={}, # no provider_fulfilment key
+ )
+
+ def test_raises_contract_error_when_fulfilment_is_none(self):
+ req = _make_request("acquire", requested_count=2)
+ machines = [_make_machine(MachineStatus.RUNNING)] * 2
+ with pytest.raises(ProviderContractError):
+ self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={"provider_fulfilment": None},
+ )
+
+ def test_legacy_fleet_capacity_key_ignored(self):
+ """Old fleet_capacity_fulfilment key must NOT unlock a legacy path."""
+ req = _make_request("acquire", requested_count=2)
+ machines = [_make_machine(MachineStatus.RUNNING)] * 2
+ with pytest.raises(ProviderContractError):
+ self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={
+ "fleet_capacity_fulfilment": {
+ "target_capacity_units": 2,
+ "fulfilled_capacity_units": 2.0,
+ }
+ },
+ )
+
+
+# ---------------------------------------------------------------------------
+# Return path — machine-state counting (unchanged)
+# ---------------------------------------------------------------------------
+
+
class TestReturnRequestCompletion:
def setup_method(self):
self.svc = _make_service()
@@ -84,3 +214,136 @@ def test_return_request_with_all_stopped_is_complete(self):
provider_metadata={},
)
assert status == RequestStatus.COMPLETED.value
+
+ def test_return_request_mix_shutting_down_and_running_is_in_progress(self):
+ """Some shutting-down, some running → IN_PROGRESS (not complete)."""
+ machines = [
+ _make_machine(MachineStatus.SHUTTING_DOWN),
+ _make_machine(MachineStatus.RUNNING),
+ ]
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=self.req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.IN_PROGRESS.value
+
+ def test_return_request_empty_provider_machines_is_complete(self):
+ """No instances visible in provider → all gone, COMPLETED."""
+ db_machines = [_make_machine(MachineStatus.TERMINATED)]
+ status, msg = self.svc.determine_status_from_machines(
+ db_machines=db_machines, # type: ignore[arg-type]
+ provider_machines=[],
+ request=self.req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.COMPLETED.value
+ assert "no longer visible" in (msg or "")
+
+
+class TestPrematureCompletedRegression:
+ """Regression guard: COMPLETED must NOT be written when termination is merely accepted."""
+
+ def setup_method(self):
+ self.svc = _make_service()
+ self.req = _make_request("return")
+
+ def test_shutting_down_instance_yields_in_progress_not_completed(self):
+ machines = [_make_machine(MachineStatus.SHUTTING_DOWN)]
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=self.req,
+ provider_metadata={},
+ )
+ assert status != RequestStatus.COMPLETED.value
+ assert status == RequestStatus.IN_PROGRESS.value
+
+ def test_mix_shutting_down_terminated_yields_in_progress(self):
+ machines = [
+ _make_machine(MachineStatus.SHUTTING_DOWN),
+ _make_machine(MachineStatus.TERMINATED),
+ _make_machine(MachineStatus.SHUTTING_DOWN),
+ ]
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=self.req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.IN_PROGRESS.value
+
+ def test_all_terminated_yields_completed(self):
+ machines = [
+ _make_machine(MachineStatus.TERMINATED),
+ _make_machine(MachineStatus.TERMINATED),
+ _make_machine(MachineStatus.TERMINATED),
+ ]
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=self.req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.COMPLETED.value
+
+
+class TestReturnPartialDescribeGuard:
+ """Regression guard: COMPLETED must NOT fire when describe returns fewer machines."""
+
+ def setup_method(self):
+ self.svc = _make_service()
+
+ def test_partial_describe_terminated_not_complete(self):
+ """3 terminated visible, requested_count=4 → IN_PROGRESS (1 not yet in response)."""
+ req = _make_request("return", requested_count=4)
+ machines = [
+ _make_machine(MachineStatus.TERMINATED),
+ _make_machine(MachineStatus.TERMINATED),
+ _make_machine(MachineStatus.TERMINATED),
+ ]
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={},
+ )
+ assert status != RequestStatus.COMPLETED.value
+ assert status == RequestStatus.IN_PROGRESS.value
+
+ def test_all_requested_terminated_is_completed(self):
+ req = _make_request("return", requested_count=4)
+ machines = [_make_machine(MachineStatus.TERMINATED)] * 4
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.COMPLETED.value
+
+ def test_more_terminated_than_requested_is_completed(self):
+ req = _make_request("return", requested_count=2)
+ machines = [_make_machine(MachineStatus.TERMINATED)] * 3
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.COMPLETED.value
+
+ def test_one_terminated_one_shutting_down_not_complete(self):
+ req = _make_request("return", requested_count=2)
+ machines = [
+ _make_machine(MachineStatus.TERMINATED),
+ _make_machine(MachineStatus.SHUTTING_DOWN),
+ ]
+ status, _ = self.svc.determine_status_from_machines(
+ db_machines=machines, # type: ignore[arg-type]
+ provider_machines=machines, # type: ignore[arg-type]
+ request=req,
+ provider_metadata={},
+ )
+ assert status == RequestStatus.IN_PROGRESS.value
diff --git a/tests/unit/application/services/test_template_defaults_service.py b/tests/unit/application/services/test_template_defaults_service.py
new file mode 100644
index 000000000..e3db69277
--- /dev/null
+++ b/tests/unit/application/services/test_template_defaults_service.py
@@ -0,0 +1,153 @@
+"""Tests for TemplateDefaultsService — registry delegation and default-API chain."""
+
+from __future__ import annotations
+
+from unittest.mock import MagicMock
+
+from orb.application.services.template_defaults_service import TemplateDefaultsService
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _make_service(
+ provider_defaults: dict | None = None,
+ global_config: dict | None = None,
+ provider_registry: object | None = None,
+) -> TemplateDefaultsService:
+ """Build a TemplateDefaultsService with controllable config mocks."""
+ logger = MagicMock()
+ config_manager = MagicMock()
+
+ # Mimic get_template_config() returning a dict (no model_dump needed).
+ config_manager.get_template_config.return_value = global_config or {}
+
+ # Mimic get_provider_config().provider_defaults.get(provider_type).
+ if provider_defaults is not None:
+ mock_provider_config = MagicMock()
+ mock_provider_config.provider_defaults = provider_defaults
+ mock_provider_config.providers = []
+ else:
+ mock_provider_config = MagicMock(provider_defaults={}, providers=[])
+ config_manager.get_provider_config.return_value = mock_provider_config
+
+ return TemplateDefaultsService(
+ config_manager=config_manager,
+ logger=logger,
+ provider_registry=provider_registry, # type: ignore[arg-type]
+ )
+
+
+def _make_provider_defaults_with_api(provider_api: str | None) -> MagicMock:
+ """Create a provider_defaults mock with template_defaults.provider_api set."""
+ defaults_obj = MagicMock()
+ template_defaults: dict = {}
+ if provider_api is not None:
+ template_defaults["provider_api"] = provider_api
+ defaults_obj.template_defaults = template_defaults
+ return defaults_obj
+
+
+# ---------------------------------------------------------------------------
+# _get_provider_type_defaults — registry delegation
+# ---------------------------------------------------------------------------
+
+
+class TestGetProviderTypeDefaultsDelegatesRegistry:
+ """When template_defaults has no provider_api, registry is consulted."""
+
+ def test_registry_default_api_fills_missing_provider_api(self):
+ """Registry.get_default_api() result is injected when template_defaults lacks provider_api."""
+ registry = MagicMock()
+ registry.get_default_api.return_value = "EC2Fleet"
+
+ provider_defaults_obj = _make_provider_defaults_with_api(None)
+ svc = _make_service(
+ provider_defaults={"aws": provider_defaults_obj},
+ provider_registry=registry,
+ )
+
+ result = svc._get_provider_type_defaults("aws")
+
+ assert result.get("provider_api") == "EC2Fleet"
+ registry.get_default_api.assert_called_once_with("aws")
+
+ def test_registry_not_called_when_template_defaults_has_provider_api(self):
+ """Registry is NOT consulted when template_defaults already has provider_api."""
+ registry = MagicMock()
+ registry.get_default_api.return_value = "SpotFleet"
+
+ provider_defaults_obj = _make_provider_defaults_with_api("EC2Fleet")
+ svc = _make_service(
+ provider_defaults={"aws": provider_defaults_obj},
+ provider_registry=registry,
+ )
+
+ result = svc._get_provider_type_defaults("aws")
+
+ assert result.get("provider_api") == "EC2Fleet"
+ registry.get_default_api.assert_not_called()
+
+ def test_no_registry_injected_returns_empty_provider_api(self):
+ """Without registry, if template_defaults has no provider_api, result has none."""
+ provider_defaults_obj = _make_provider_defaults_with_api(None)
+ svc = _make_service(
+ provider_defaults={"aws": provider_defaults_obj},
+ provider_registry=None,
+ )
+
+ result = svc._get_provider_type_defaults("aws")
+
+ assert "provider_api" not in result
+
+ def test_registry_returns_none_leaves_provider_api_absent(self):
+ """When registry returns None, provider_api is not added to result."""
+ registry = MagicMock()
+ registry.get_default_api.return_value = None
+
+ provider_defaults_obj = _make_provider_defaults_with_api(None)
+ svc = _make_service(
+ provider_defaults={"aws": provider_defaults_obj},
+ provider_registry=registry,
+ )
+
+ result = svc._get_provider_type_defaults("aws")
+
+ assert "provider_api" not in result
+
+ def test_unknown_provider_type_returns_empty_dict(self):
+ """A provider type with no registration returns {}."""
+ registry = MagicMock()
+ registry.get_default_api.return_value = None
+ svc = _make_service(provider_defaults={}, provider_registry=registry)
+
+ result = svc._get_provider_type_defaults("unknown-provider")
+
+ assert result == {}
+
+
+# ---------------------------------------------------------------------------
+# TemplateDefaultsService init — provider_registry optional
+# ---------------------------------------------------------------------------
+
+
+class TestTemplateDefaultsServiceInit:
+ def test_no_registry_param_accepted(self):
+ """Service can be instantiated without provider_registry (backward compat)."""
+ logger = MagicMock()
+ config_manager = MagicMock()
+ config_manager.get_template_config.return_value = {}
+ config_manager.get_provider_config.return_value = MagicMock(
+ provider_defaults={}, providers=[]
+ )
+
+ svc = TemplateDefaultsService(config_manager=config_manager, logger=logger)
+
+ assert svc.provider_registry is None
+
+ def test_registry_stored_on_instance(self):
+ """Injected provider_registry is accessible via attribute."""
+ registry = MagicMock()
+ svc = _make_service(provider_registry=registry)
+ assert svc.provider_registry is registry
diff --git a/tests/unit/application/test_cqrs_patterns.py b/tests/unit/application/test_cqrs_patterns.py
index d42505a97..ef3ac51e8 100644
--- a/tests/unit/application/test_cqrs_patterns.py
+++ b/tests/unit/application/test_cqrs_patterns.py
@@ -168,6 +168,15 @@ async def test_command_handlers_modify_state(self):
mock_provisioning_service = Mock()
+ mock_selection_result = Mock()
+ mock_selection_result.provider_type = "aws"
+ mock_selection_result.provider_name = "test-provider"
+ mock_selection_result.selection_reason = "test"
+ mock_provider_validation_service = AsyncMock()
+ mock_provider_validation_service.select_and_validate_provider.return_value = (
+ mock_selection_result
+ )
+
handler = CreateRequestHandler(
uow_factory=mock_uow_factory,
logger=mock_logger,
@@ -177,6 +186,7 @@ async def test_command_handlers_modify_state(self):
query_bus=mock_query_bus,
provider_selection_port=mock_provider_selection,
provisioning_service=mock_provisioning_service,
+ provider_validation_service=mock_provider_validation_service,
)
# Execute command with dry_run — PENDING→COMPLETED is now valid
@@ -470,6 +480,17 @@ async def test_create_request_handler_validates_input(self):
mock_provider_port = Mock()
mock_provider_port.available_strategies = ["test-strategy"]
+ mock_selection_result = Mock()
+ mock_selection_result.provider_instance = "test-provider"
+ mock_selection_result.provider_type = "aws"
+ mock_selection_result.provider_name = "test-provider"
+ mock_selection_result.selection_reason = "test"
+ mock_selection_result.confidence = 1.0
+ mock_provider_validation_service = AsyncMock()
+ mock_provider_validation_service.select_and_validate_provider.return_value = (
+ mock_selection_result
+ )
+
handler = CreateRequestHandler(
uow_factory=mock_uow_factory,
logger=mock_logger,
@@ -479,6 +500,7 @@ async def test_create_request_handler_validates_input(self):
query_bus=mock_query_bus,
provider_selection_port=mock_provider_selection,
provisioning_service=Mock(),
+ provider_validation_service=mock_provider_validation_service,
)
# Valid command
@@ -493,20 +515,6 @@ async def test_create_request_handler_validates_input(self):
mock_template.to_dict.return_value = {"template_id": "test-template"}
mock_query_bus.execute.return_value = mock_template
- mock_selection_result = Mock()
- mock_selection_result.provider_instance = "test-provider"
- mock_selection_result.provider_type = "aws"
- mock_selection_result.provider_name = "test-provider"
- mock_selection_result.selection_reason = "test"
- mock_selection_result.confidence = 1.0
- mock_provider_selection.select_provider_for_template.return_value = mock_selection_result
- mock_provider_validation_result = Mock()
- mock_provider_validation_result.is_valid = True
- mock_provider_validation_result.warnings = []
- mock_provider_selection.validate_template_requirements.return_value = (
- mock_provider_validation_result
- )
-
mock_provider_capability = Mock()
mock_validation_result = Mock()
mock_validation_result.is_valid = True
@@ -564,21 +572,9 @@ async def test_command_handlers_are_transactional(self):
mock_selection_result.provider_name = "test-provider"
mock_selection_result.selection_reason = "test"
mock_selection_result.confidence = 1.0
- mock_provider_selection.select_provider_for_template.return_value = mock_selection_result
- mock_provider_validation_result = Mock()
- mock_provider_validation_result.is_valid = True
- mock_provider_validation_result.warnings = []
- mock_provider_selection.validate_template_requirements.return_value = (
- mock_provider_validation_result
- )
-
- mock_provider_capability = Mock()
- mock_validation_result = Mock()
- mock_validation_result.is_valid = True
- mock_validation_result.supported_features = []
- mock_validation_result.warnings = []
- mock_provider_capability.validate_template_requirements.return_value = (
- mock_validation_result
+ mock_provider_validation_service = AsyncMock()
+ mock_provider_validation_service.select_and_validate_provider.return_value = (
+ mock_selection_result
)
mock_provider_port = Mock()
@@ -593,6 +589,7 @@ async def test_command_handlers_are_transactional(self):
query_bus=mock_query_bus,
provider_selection_port=mock_provider_selection,
provisioning_service=Mock(),
+ provider_validation_service=mock_provider_validation_service,
)
# Execute command with dry_run — PENDING→COMPLETED is now valid
@@ -634,6 +631,7 @@ def test_command_handlers_publish_events(self):
query_bus=mock_query_bus,
provider_selection_port=mock_provider_selection,
provisioning_service=Mock(),
+ provider_validation_service=Mock(),
)
# Should have event publisher
diff --git a/tests/unit/architecture/test_boto3_leak_detection.py b/tests/unit/architecture/test_boto3_leak_detection.py
new file mode 100644
index 000000000..d6ae7b171
--- /dev/null
+++ b/tests/unit/architecture/test_boto3_leak_detection.py
@@ -0,0 +1,59 @@
+"""Architecture test: boto3 and botocore must not be imported outside providers/aws/.
+
+Importing boto3 or botocore directly in core or infrastructure modules couples the
+entire application to the [aws] extra even when it is not installed. All boto3/botocore
+usage must be confined to src/orb/providers/aws/.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import pytest
+
+from tests.unit.architecture.conftest import (
+ EXCEPTION_PATHS,
+ SRC_ORB,
+ collect_python_files,
+ extract_imports,
+)
+
+_PROVIDERS_AWS_DIR = SRC_ORB / "providers" / "aws"
+
+# All source files that live outside providers/aws/
+_NON_AWS_FILES = [
+ f
+ for f in collect_python_files(SRC_ORB)
+ if not f.is_relative_to(_PROVIDERS_AWS_DIR) and str(f) not in EXCEPTION_PATHS
+]
+
+# Top-level module names that constitute direct AWS SDK imports
+_AWS_SDK_MODULES = frozenset({"boto3", "botocore"})
+
+# Known violations — files currently allowed to import boto3/botocore outside providers/aws/.
+# This set should remain empty; add entries only as a last resort with a tracking comment.
+_KNOWN_VIOLATIONS: frozenset[tuple[str, str]] = frozenset()
+
+
+@pytest.mark.parametrize(
+ "filepath",
+ _NON_AWS_FILES,
+ ids=lambda p: str(p.relative_to(SRC_ORB)),
+)
+@pytest.mark.unit
+@pytest.mark.architecture
+def test_no_boto3_outside_aws_provider(filepath: Path) -> None:
+ """boto3/botocore imports must not appear outside src/orb/providers/aws/."""
+ rel = str(filepath.relative_to(SRC_ORB))
+ imports = extract_imports(filepath)
+ new_violations = [
+ imp
+ for imp in imports
+ if (imp in _AWS_SDK_MODULES or any(imp.startswith(f"{m}.") for m in _AWS_SDK_MODULES))
+ and (rel, imp) not in _KNOWN_VIOLATIONS
+ ]
+ assert new_violations == [], (
+ f"{rel} imports boto3/botocore outside providers/aws/ — "
+ f"move to providers/aws/ or guard with try/except ImportError. "
+ f"Violations: {new_violations}"
+ )
diff --git a/tests/unit/architecture/test_cli_infrastructure_boundary.py b/tests/unit/architecture/test_cli_infrastructure_boundary.py
index d227be818..408229eb4 100644
--- a/tests/unit/architecture/test_cli_infrastructure_boundary.py
+++ b/tests/unit/architecture/test_cli_infrastructure_boundary.py
@@ -34,6 +34,7 @@
("cli/main.py", "orb.infrastructure.mocking.dry_run_context"),
("cli/main.py", "orb.infrastructure.di.container"),
("cli/factories/provider_command_factory.py", "orb.infrastructure.utilities.json_utils"),
+ ("cli/args.py", "orb.infrastructure.registry.cli_spec_registry"),
}
)
diff --git a/tests/unit/architecture/test_domain_purity.py b/tests/unit/architecture/test_domain_purity.py
index dfebe7261..aa587a3a1 100644
--- a/tests/unit/architecture/test_domain_purity.py
+++ b/tests/unit/architecture/test_domain_purity.py
@@ -27,7 +27,6 @@
_KNOWN_PYDANTIC_VIOLATIONS: frozenset[tuple[str, str]] = frozenset(
{
("domain/template/template_aggregate.py", "pydantic"),
- ("domain/template/extensions.py", "pydantic"),
("domain/machine/aggregate.py", "pydantic"),
("domain/machine/machine_metadata.py", "pydantic"),
("domain/machine/machine_identifiers.py", "pydantic"),
diff --git a/tests/unit/architecture/test_interface_provider_boundary.py b/tests/unit/architecture/test_interface_provider_boundary.py
index c6352887c..cf9799285 100644
--- a/tests/unit/architecture/test_interface_provider_boundary.py
+++ b/tests/unit/architecture/test_interface_provider_boundary.py
@@ -41,8 +41,9 @@
("interface/init_command_handler.py", "orb.providers.factory"),
("interface/machine_command_handlers.py", "orb.providers.base.strategy"),
("interface/mcp/server/core.py", "orb.providers.registry"),
- ("api/server.py", "orb.providers.aws.auth.iam_strategy"),
- ("api/server.py", "orb.providers.aws.auth.cognito_strategy"),
+ # CLI spec bootstrap: build_parser triggers lightweight CLI-spec registration
+ # so that provider flags (e.g. --aws-profile) are available before app init.
+ ("cli/args.py", "orb.providers.registration"),
}
)
diff --git a/tests/unit/architecture/test_provider_leak_detection.py b/tests/unit/architecture/test_provider_leak_detection.py
index 048c2e058..73035a819 100644
--- a/tests/unit/architecture/test_provider_leak_detection.py
+++ b/tests/unit/architecture/test_provider_leak_detection.py
@@ -35,9 +35,9 @@
_KNOWN_VIOLATIONS: frozenset[tuple[str, str]] = frozenset(
{
("bootstrap/core_services.py", "orb.providers.registry"),
- ("bootstrap/infrastructure_services.py", "orb.providers.aws.registration"),
+ ("bootstrap/infrastructure_services.py", "orb.providers.registration"),
("bootstrap/provider_services.py", "orb.providers.registry"),
- ("bootstrap/provider_services.py", "orb.providers.aws.registration"),
+ ("bootstrap/provider_services.py", "orb.providers.registration"),
("bootstrap/services.py", "orb.providers.registration"),
("interface/health_command_handler.py", "orb.providers.registry"),
("interface/system_command_handlers.py", "orb.providers.registry"),
@@ -48,8 +48,6 @@
("interface/init_command_handler.py", "orb.providers.factory"),
("interface/machine_command_handlers.py", "orb.providers.base.strategy"),
("interface/mcp/server/core.py", "orb.providers.registry"),
- ("api/server.py", "orb.providers.aws.auth.iam_strategy"),
- ("api/server.py", "orb.providers.aws.auth.cognito_strategy"),
("config/managers/provider_manager.py", "orb.providers.registry"),
("application/services/provider_registry_service.py", "orb.providers.registry"),
("infrastructure/template/configuration_manager.py", "orb.providers.base.strategy"),
@@ -57,18 +55,17 @@
("infrastructure/adapters/provider_discovery_adapter.py", "orb.providers.registry"),
# DI wiring — intentional: storage registration must wire AWS provider
("infrastructure/storage/registration.py", "orb.providers.aws.storage.registration"),
- # hostfactory is inherently AWS/HPC-specific — provider import is expected
- (
- "infrastructure/scheduler/hostfactory/field_mapper.py",
- "orb.providers.aws.utilities.ec2.instances",
- ),
- (
- "infrastructure/scheduler/hostfactory/hostfactory_strategy.py",
- "orb.providers.aws.utilities.ec2.instances",
- ),
+ # The cpu/ram lookup (derive_cpu_ram_from_instance_type) has been moved
+ # into providers/aws/scheduler/hostfactory_field_mapping.py. The shared
+ # hostfactory infrastructure no longer imports from orb.providers.aws.*.
("config/schemas/cleanup_schema.py", "orb.providers.aws.configuration.cleanup_config"),
# loader collects strategy-contributed defaults at load time — intentional bootstrap wiring
("config/loader.py", "orb.providers.registry"),
+ ("config/loader.py", "orb.providers"),
+ ("config/loader.py", "orb.providers.registration"),
+ # CLI spec bootstrap: build_parser triggers lightweight CLI-spec registration
+ # so that provider flags (e.g. --aws-profile) are available before app init.
+ ("cli/args.py", "orb.providers.registration"),
}
)
diff --git a/tests/unit/architecture/violation_counts.json b/tests/unit/architecture/violation_counts.json
index 139d1834a..a5f14bc5a 100644
--- a/tests/unit/architecture/violation_counts.json
+++ b/tests/unit/architecture/violation_counts.json
@@ -4,8 +4,8 @@
"provider_leaks_non_di": 29,
"scheduler_leaks": 0,
"storage_leaks_non_storage": 4,
- "domain_pydantic_violations": 13,
+ "domain_pydantic_violations": 12,
"interface_provider_direct_imports": 16,
"circular_dependency_cycles": 18,
- "cli_infrastructure_direct_imports": 13
+ "cli_infrastructure_direct_imports": 14
}
diff --git a/tests/unit/config/test_server_schema.py b/tests/unit/config/test_server_schema.py
new file mode 100644
index 000000000..e0b552a6f
--- /dev/null
+++ b/tests/unit/config/test_server_schema.py
@@ -0,0 +1,52 @@
+"""Unit tests for server_schema configuration models."""
+
+from __future__ import annotations
+
+import pytest
+from pydantic import ValidationError
+
+# ---------------------------------------------------------------------------
+# BearerTokenAuthSubConfig — algorithm allowlist validator
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_bearer_token_config_default_algorithm_is_hs256():
+ """Default algorithm is HS256."""
+ from orb.config.schemas.server_schema import BearerTokenAuthSubConfig
+
+ cfg = BearerTokenAuthSubConfig(secret_key="a" * 32) # type: ignore[call-arg] # pydantic default fields
+ assert cfg.algorithm == "HS256"
+
+
+@pytest.mark.unit
+@pytest.mark.parametrize("algo", ["HS256", "HS384", "HS512"])
+def test_bearer_token_config_valid_algorithms(algo: str):
+ """HS256, HS384, and HS512 are all accepted."""
+ from orb.config.schemas.server_schema import BearerTokenAuthSubConfig
+
+ cfg = BearerTokenAuthSubConfig(secret_key="a" * 32, algorithm=algo) # type: ignore[call-arg] # pydantic default fields
+ assert cfg.algorithm == algo
+
+
+@pytest.mark.unit
+@pytest.mark.parametrize("algo", ["none", "None", "NONE"])
+def test_bearer_token_config_rejects_none_algorithm(algo: str):
+ """algorithm='none' (any casing) raises ValidationError with a descriptive message."""
+ from orb.config.schemas.server_schema import BearerTokenAuthSubConfig
+
+ with pytest.raises(ValidationError) as exc_info:
+ BearerTokenAuthSubConfig(secret_key="a" * 32, algorithm=algo) # type: ignore[call-arg] # pydantic default fields
+
+ errors = exc_info.value.errors()
+ assert any("none" in str(e).lower() for e in errors)
+
+
+@pytest.mark.unit
+@pytest.mark.parametrize("algo", ["RS256", "ES256", "HS1", "unknown"])
+def test_bearer_token_config_rejects_unsupported_algorithms(algo: str):
+ """Non-HMAC and unknown algorithms raise ValidationError."""
+ from orb.config.schemas.server_schema import BearerTokenAuthSubConfig
+
+ with pytest.raises(ValidationError):
+ BearerTokenAuthSubConfig(secret_key="a" * 32, algorithm=algo) # type: ignore[call-arg] # pydantic default fields
diff --git a/tests/unit/infrastructure/auth/test_enhanced_bearer_token.py b/tests/unit/infrastructure/auth/test_enhanced_bearer_token.py
new file mode 100644
index 000000000..ea94558e3
--- /dev/null
+++ b/tests/unit/infrastructure/auth/test_enhanced_bearer_token.py
@@ -0,0 +1,242 @@
+"""Unit tests for EnhancedBearerTokenStrategy."""
+
+from __future__ import annotations
+
+import time
+from typing import Any
+
+import jwt
+import pytest
+
+from orb.infrastructure.adapters.ports.auth import AuthContext, AuthStatus
+from orb.infrastructure.auth.strategy.bearer_token_strategy_enhanced import (
+ EnhancedBearerTokenStrategy,
+)
+from orb.infrastructure.auth.token_denylist.in_memory_denylist import InMemoryTokenDenylist
+
+_SECRET = "a" * 32 # exactly 32 bytes — minimum valid length
+
+
+def _make_denylist() -> InMemoryTokenDenylist:
+ return InMemoryTokenDenylist()
+
+
+def _make_strategy(
+ denylist: InMemoryTokenDenylist | None = None,
+ rate_limit_enabled: bool = False,
+) -> EnhancedBearerTokenStrategy:
+ return EnhancedBearerTokenStrategy(
+ secret_key=_SECRET,
+ denylist=denylist or _make_denylist(),
+ rate_limit_enabled=rate_limit_enabled,
+ )
+
+
+def _make_context(**kwargs) -> AuthContext:
+ defaults: dict[str, Any] = {
+ "method": "GET",
+ "path": "/api/v1/machines",
+ "headers": {},
+ "query_params": {},
+ "client_ip": "127.0.0.1",
+ }
+ defaults.update(kwargs)
+ return AuthContext(**defaults)
+
+
+def _make_token(
+ user_id: str = "user-1",
+ roles: list[str] | None = None,
+ expiry_offset: int = 3600,
+) -> str:
+ now = int(time.time())
+ payload = {
+ "sub": user_id,
+ "roles": roles or ["user"],
+ "permissions": [],
+ "type": "access",
+ "iat": now,
+ "exp": now + expiry_offset,
+ "iss": "open-resource-broker",
+ }
+ return jwt.encode(payload, _SECRET, algorithm="HS256")
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — happy path
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_enhanced_bearer_authenticate_success():
+ """authenticate() returns SUCCESS for a valid Bearer token."""
+ strategy = _make_strategy()
+ token = _make_token()
+
+ result = await strategy.authenticate(
+ _make_context(headers={"authorization": f"Bearer {token}"})
+ )
+
+ assert result.status == AuthStatus.SUCCESS
+ assert result.user_id == "user-1"
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — missing header
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_enhanced_bearer_authenticate_missing_header():
+ """authenticate() returns FAILED when Authorization header is absent."""
+ strategy = _make_strategy()
+
+ result = await strategy.authenticate(_make_context(headers={}))
+
+ assert result.status == AuthStatus.FAILED
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — denylisted token
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_enhanced_bearer_authenticate_denylisted_token():
+ """validate_token() returns INVALID for a denylisted token."""
+ denylist = _make_denylist()
+ strategy = _make_strategy(denylist=denylist)
+ token = _make_token()
+
+ await denylist.add_token(token)
+ result = await strategy.validate_token(token)
+
+ assert result.status == AuthStatus.INVALID
+ assert "revoked" in (result.error_message or "").lower()
+
+
+# ---------------------------------------------------------------------------
+# authenticate() — rate limit
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_enhanced_bearer_rate_limit():
+ """authenticate() returns FAILED when rate limit is exceeded."""
+ strategy = EnhancedBearerTokenStrategy(
+ secret_key=_SECRET,
+ denylist=_make_denylist(),
+ rate_limit_enabled=True,
+ max_attempts=1,
+ rate_window=60,
+ )
+
+ ctx = _make_context(headers={"authorization": "Bearer invalid"})
+
+ # First attempt consumes the single allowed slot (will fail on bad token, not rate limit)
+ await strategy.authenticate(ctx)
+
+ # Second attempt should hit the rate limit
+ result = await strategy.authenticate(ctx)
+ assert result.status == AuthStatus.FAILED
+ assert "too many" in (result.error_message or "").lower()
+
+
+# ---------------------------------------------------------------------------
+# revoke_token()
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_enhanced_bearer_revoke_token():
+ """revoke_token() adds the token to the denylist."""
+ denylist = _make_denylist()
+ strategy = _make_strategy(denylist=denylist)
+ token = _make_token()
+
+ success = await strategy.revoke_token(token)
+ assert success is True
+ assert await denylist.is_denylisted(token) is True
+
+
+# ---------------------------------------------------------------------------
+# validate_token() — invalid signature
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_enhanced_bearer_invalid_signature():
+ """validate_token() returns INVALID for a token signed with a different key."""
+ strategy = _make_strategy()
+ wrong_token = jwt.encode(
+ {"sub": "x", "iat": int(time.time()), "exp": int(time.time()) + 60},
+ "b" * 32,
+ algorithm="HS256",
+ )
+
+ result = await strategy.validate_token(wrong_token)
+
+ assert result.status == AuthStatus.INVALID
+
+
+# ---------------------------------------------------------------------------
+# from_auth_config classmethod
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_enhanced_bearer_from_auth_config():
+ """from_auth_config builds strategy from typed BearerTokenAuthSubConfig."""
+ from orb.config.schemas.server_schema import AuthConfig, BearerTokenAuthSubConfig
+
+ auth_config = AuthConfig( # type: ignore[call-arg] # pydantic default fields
+ strategy="bearer_token_enhanced",
+ bearer_token=BearerTokenAuthSubConfig( # type: ignore[call-arg] # pydantic default fields
+ secret_key=_SECRET
+ ),
+ )
+
+ strategy = EnhancedBearerTokenStrategy.from_auth_config(auth_config)
+
+ assert strategy.secret_key == _SECRET
+ assert strategy.enabled is True
+
+
+@pytest.mark.unit
+def test_enhanced_bearer_from_auth_config_missing_bearer_token():
+ """from_auth_config raises ConfigurationError when bearer_token sub-config is absent."""
+ from orb.config.schemas.server_schema import AuthConfig
+ from orb.domain.base.exceptions import ConfigurationError
+
+ auth_config = AuthConfig(strategy="bearer_token_enhanced") # type: ignore[call-arg] # pydantic default fields
+
+ with pytest.raises(ConfigurationError):
+ EnhancedBearerTokenStrategy.from_auth_config(auth_config)
+
+
+# ---------------------------------------------------------------------------
+# Short secret key → ConfigurationError (not a warning)
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+def test_enhanced_bearer_short_key_raises_configuration_error():
+ """
+ EnhancedBearerTokenStrategy must raise ConfigurationError for keys shorter
+ than 32 bytes, matching the error policy of BearerTokenStrategy.
+ """
+ from orb.domain.base.exceptions import ConfigurationError
+
+ short_key = "a" * 16 # 16 bytes — below the 32-byte minimum
+
+ with pytest.raises(ConfigurationError, match="32 bytes"):
+ EnhancedBearerTokenStrategy(
+ secret_key=short_key,
+ denylist=_make_denylist(),
+ )
diff --git a/tests/unit/infrastructure/auth/test_token_blacklist.py b/tests/unit/infrastructure/auth/test_token_blacklist.py
deleted file mode 100644
index 58e9619de..000000000
--- a/tests/unit/infrastructure/auth/test_token_blacklist.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""Tests for token blacklist implementations."""
-
-import time
-
-import pytest
-
-from orb.infrastructure.auth.token_blacklist import InMemoryTokenBlacklist
-
-
-@pytest.mark.asyncio
-async def test_in_memory_blacklist_add_token():
- """Test adding token to blacklist."""
- blacklist = InMemoryTokenBlacklist()
-
- token = "test_token_123"
- expires_at = int(time.time()) + 3600
-
- result = await blacklist.add_token(token, expires_at)
- assert result is True
-
- is_blacklisted = await blacklist.is_blacklisted(token)
- assert is_blacklisted is True
-
-
-@pytest.mark.asyncio
-async def test_in_memory_blacklist_remove_token():
- """Test removing token from blacklist."""
- blacklist = InMemoryTokenBlacklist()
-
- token = "test_token_123"
- await blacklist.add_token(token)
-
- result = await blacklist.remove_token(token)
- assert result is True
-
- is_blacklisted = await blacklist.is_blacklisted(token)
- assert is_blacklisted is False
-
-
-@pytest.mark.asyncio
-async def test_in_memory_blacklist_expired_token():
- """Test that expired tokens are automatically removed."""
- blacklist = InMemoryTokenBlacklist()
-
- token = "test_token_123"
- expires_at = int(time.time()) - 1 # Already expired
-
- await blacklist.add_token(token, expires_at)
-
- # Token should be removed when checked
- is_blacklisted = await blacklist.is_blacklisted(token)
- assert is_blacklisted is False
-
-
-@pytest.mark.asyncio
-async def test_in_memory_blacklist_cleanup():
- """Test cleanup of expired tokens."""
- blacklist = InMemoryTokenBlacklist()
-
- # Add expired token
- expired_token = "expired_token"
- await blacklist.add_token(expired_token, int(time.time()) - 1)
-
- # Add valid token
- valid_token = "valid_token"
- await blacklist.add_token(valid_token, int(time.time()) + 3600)
-
- # Run cleanup
- removed = await blacklist.cleanup_expired()
- assert removed == 1
-
- # Valid token should still be there
- assert await blacklist.is_blacklisted(valid_token) is True
-
-
-@pytest.mark.asyncio
-async def test_in_memory_blacklist_size():
- """Test getting blacklist size."""
- blacklist = InMemoryTokenBlacklist()
-
- assert await blacklist.get_blacklist_size() == 0
-
- await blacklist.add_token("token1")
- await blacklist.add_token("token2")
-
- assert await blacklist.get_blacklist_size() == 2
diff --git a/tests/unit/infrastructure/auth/test_token_denylist.py b/tests/unit/infrastructure/auth/test_token_denylist.py
new file mode 100644
index 000000000..a11b0d2e6
--- /dev/null
+++ b/tests/unit/infrastructure/auth/test_token_denylist.py
@@ -0,0 +1,86 @@
+"""Tests for token denylist implementations."""
+
+import time
+
+import pytest
+
+from orb.infrastructure.auth.token_denylist import InMemoryTokenDenylist
+
+
+@pytest.mark.asyncio
+async def test_in_memory_denylist_add_token():
+ """Test adding token to denylist."""
+ denylist = InMemoryTokenDenylist()
+
+ token = "test_token_123"
+ expires_at = int(time.time()) + 3600
+
+ result = await denylist.add_token(token, expires_at)
+ assert result is True
+
+ is_denylisted = await denylist.is_denylisted(token)
+ assert is_denylisted is True
+
+
+@pytest.mark.asyncio
+async def test_in_memory_denylist_remove_token():
+ """Test removing token from denylist."""
+ denylist = InMemoryTokenDenylist()
+
+ token = "test_token_123"
+ await denylist.add_token(token)
+
+ result = await denylist.remove_token(token)
+ assert result is True
+
+ is_denylisted = await denylist.is_denylisted(token)
+ assert is_denylisted is False
+
+
+@pytest.mark.asyncio
+async def test_in_memory_denylist_expired_token():
+ """Test that expired tokens are automatically removed."""
+ denylist = InMemoryTokenDenylist()
+
+ token = "test_token_123"
+ expires_at = int(time.time()) - 1 # Already expired
+
+ await denylist.add_token(token, expires_at)
+
+ # Token should be removed when checked
+ is_denylisted = await denylist.is_denylisted(token)
+ assert is_denylisted is False
+
+
+@pytest.mark.asyncio
+async def test_in_memory_denylist_cleanup():
+ """Test cleanup of expired tokens."""
+ denylist = InMemoryTokenDenylist()
+
+ # Add expired token
+ expired_token = "expired_token"
+ await denylist.add_token(expired_token, int(time.time()) - 1)
+
+ # Add valid token
+ valid_token = "valid_token"
+ await denylist.add_token(valid_token, int(time.time()) + 3600)
+
+ # Run cleanup
+ removed = await denylist.cleanup_expired()
+ assert removed == 1
+
+ # Valid token should still be there
+ assert await denylist.is_denylisted(valid_token) is True
+
+
+@pytest.mark.asyncio
+async def test_in_memory_denylist_size():
+ """Test getting denylist size."""
+ denylist = InMemoryTokenDenylist()
+
+ assert await denylist.get_denylist_size() == 0
+
+ await denylist.add_token("token1")
+ await denylist.add_token("token2")
+
+ assert await denylist.get_denylist_size() == 2
diff --git a/tests/unit/infrastructure/scheduler/conftest.py b/tests/unit/infrastructure/scheduler/conftest.py
index f8858e59d..bd09157e1 100644
--- a/tests/unit/infrastructure/scheduler/conftest.py
+++ b/tests/unit/infrastructure/scheduler/conftest.py
@@ -14,7 +14,7 @@
from orb.infrastructure.scheduler.hostfactory.hostfactory_strategy import (
HostFactorySchedulerStrategy,
)
-from tests.onaws.plugin_io_schemas import (
+from tests.providers.aws.live.plugin_io_schemas import (
expected_get_available_templates_schema_default,
expected_get_available_templates_schema_hostfactory,
expected_request_machines_schema_default,
diff --git a/tests/unit/infrastructure/scheduler/test_response_formatting.py b/tests/unit/infrastructure/scheduler/test_response_formatting.py
index 78cbb9c5e..c0d327441 100644
--- a/tests/unit/infrastructure/scheduler/test_response_formatting.py
+++ b/tests/unit/infrastructure/scheduler/test_response_formatting.py
@@ -25,7 +25,7 @@
HostFactorySchedulerStrategy,
)
from orb.infrastructure.template.dtos import TemplateDTO
-from tests.onaws.plugin_io_schemas import (
+from tests.providers.aws.live.plugin_io_schemas import (
expected_get_available_templates_schema_default,
expected_get_available_templates_schema_hostfactory,
expected_request_status_schema_default,
diff --git a/tests/unit/infrastructure/template/test_template_dto_no_aws_fields.py b/tests/unit/infrastructure/template/test_template_dto_no_aws_fields.py
index 05c78c563..c4ee61271 100644
--- a/tests/unit/infrastructure/template/test_template_dto_no_aws_fields.py
+++ b/tests/unit/infrastructure/template/test_template_dto_no_aws_fields.py
@@ -10,6 +10,7 @@
AWSRequiredIntegerRange,
AWSTemplate,
)
+from orb.providers.aws.domain.template.aws_template_dto_config import AWSTemplateDTOConfig
# ---------------------------------------------------------------------------
# AST scan — no top-level AWS fields on TemplateDTO
@@ -19,7 +20,13 @@
os.path.dirname(__file__),
"../../../../src/orb/infrastructure/template/dtos.py",
)
-_AWS_FIELDS = {"fleet_role", "fleet_type", "percent_on_demand", "abis_instance_requirements"}
+_AWS_FIELDS = {
+ "fleet_role",
+ "fleet_type",
+ "percent_on_demand",
+ "abis_instance_requirements",
+ "launch_template_id",
+}
def _get_template_dto_field_names() -> set[str]:
@@ -65,14 +72,27 @@ def test_abis_instance_requirements_not_a_top_level_field(self):
"abis_instance_requirements is an AWS-specific field and must not be a top-level TemplateDTO attribute"
)
+ def test_launch_template_id_not_a_top_level_field(self):
+ fields = _get_template_dto_field_names()
+ assert "launch_template_id" not in fields, (
+ "launch_template_id is an AWS-specific field and must not be a top-level TemplateDTO attribute"
+ )
+
def test_no_aws_fields_at_all(self):
fields = _get_template_dto_field_names()
present = _AWS_FIELDS & fields
assert not present, f"AWS-specific fields still declared on TemplateDTO: {present}"
+ def test_provider_config_field_present(self):
+ """TemplateDTO must expose a typed provider_config field."""
+ fields = _get_template_dto_field_names()
+ assert "provider_config" in fields, (
+ "TemplateDTO must have a provider_config field for typed provider-specific configuration"
+ )
+
# ---------------------------------------------------------------------------
-# from_domain() packs AWS fields into metadata
+# from_domain() populates typed provider_config
# ---------------------------------------------------------------------------
@@ -89,46 +109,62 @@ def _make_aws_template(**kwargs) -> AWSTemplate:
return AWSTemplate(**defaults)
-class TestFromDomainPacksAWSFieldsIntoMetadata:
- """TemplateDTO.from_domain() must move AWS fields into the metadata dict."""
+class TestFromDomainPopulatesProviderConfig:
+ """TemplateDTO.from_domain() must move AWS fields into the typed provider_config."""
- def test_fleet_type_in_metadata(self):
+ def test_provider_config_is_aws_dto_config_type(self):
template = _make_aws_template(fleet_type=AWSFleetType.MAINTAIN)
dto = TemplateDTO.from_domain(template)
- assert dto.metadata.get("fleet_type") == "maintain", (
- "fleet_type must be stored in metadata, not as a top-level field"
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig), (
+ "provider_config must be an AWSTemplateDTOConfig instance for AWS templates"
)
- def test_fleet_role_in_metadata(self):
+ def test_fleet_type_in_provider_config(self):
+ template = _make_aws_template(fleet_type=AWSFleetType.MAINTAIN)
+ dto = TemplateDTO.from_domain(template)
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig)
+ assert dto.provider_config.fleet_type == "maintain", (
+ "fleet_type must be stored in provider_config, not as a top-level field"
+ )
+
+ def test_fleet_role_in_provider_config(self):
template = _make_aws_template(fleet_role="arn:aws:iam::123:role/MyRole")
dto = TemplateDTO.from_domain(template)
- assert dto.metadata.get("fleet_role") == "arn:aws:iam::123:role/MyRole"
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig)
+ assert dto.provider_config.fleet_role == "arn:aws:iam::123:role/MyRole"
- def test_percent_on_demand_in_metadata(self):
+ def test_percent_on_demand_in_provider_config(self):
template = _make_aws_template(price_type="heterogeneous", percent_on_demand=40)
dto = TemplateDTO.from_domain(template)
- assert dto.metadata.get("percent_on_demand") == 40
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig)
+ assert dto.provider_config.percent_on_demand == 40
+
+ def test_launch_template_id_in_provider_config(self):
+ template = _make_aws_template(launch_template_id="lt-0abc123def456")
+ dto = TemplateDTO.from_domain(template)
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig)
+ assert dto.provider_config.launch_template_id == "lt-0abc123def456"
- def test_abis_instance_requirements_in_metadata(self):
+ def test_abis_instance_requirements_in_provider_config(self):
abis = ABISInstanceRequirements(
VCpuCount=AWSRequiredIntegerRange(Min=2, Max=8),
MemoryMiB=AWSRequiredIntegerRange(Min=4096, Max=16384),
)
template = _make_aws_template(abis_instance_requirements=abis)
dto = TemplateDTO.from_domain(template)
- stored = dto.metadata.get("abis_instance_requirements")
- assert stored is not None, "abis_instance_requirements must be stored in metadata"
- assert stored["VCpuCount"]["Min"] == 2
-
- def test_none_fleet_type_not_in_metadata(self):
- """When fleet_type is None it must not pollute metadata."""
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig)
+ stored = dto.provider_config.abis_instance_requirements
+ assert stored is not None, "abis_instance_requirements must be stored in provider_config"
+ # model_dump() serialises ABISInstanceRequirements with snake_case field names
+ assert stored["vcpu_count"]["min"] == 2
+
+ def test_none_fleet_type_not_polluting_metadata(self):
+ """When fleet_type is None on the domain object, metadata must stay clean."""
template = _make_aws_template(provider_api="RunInstances")
dto = TemplateDTO.from_domain(template)
- assert "fleet_type" not in dto.metadata or dto.metadata.get("fleet_type") is None or True
- # More precisely: if fleet_type is None on the domain object, metadata should not
- # have a fleet_type key (or it may have one set by AWSTemplate's own validator).
- # The key assertion is that no top-level attribute exists.
- assert not hasattr(dto, "fleet_type") or "fleet_type" not in dto.model_fields
+ assert "fleet_type" not in dto.metadata, (
+ "fleet_type must not pollute the cross-provider metadata dict"
+ )
def test_existing_metadata_preserved(self):
"""from_domain must not discard pre-existing metadata on the domain object."""
@@ -137,8 +173,32 @@ def test_existing_metadata_preserved(self):
metadata={"custom_key": "custom_value"},
)
dto = TemplateDTO.from_domain(template)
- assert dto.metadata.get("custom_key") == "custom_value"
- assert dto.metadata.get("fleet_type") == "request"
+ assert dto.metadata.get("custom_key") == "custom_value", (
+ "Pre-existing metadata keys must be preserved"
+ )
+ # fleet_type must now be in provider_config, NOT metadata
+ assert "fleet_type" not in dto.metadata, (
+ "fleet_type must not pollute the cross-provider metadata dict"
+ )
+ assert isinstance(dto.provider_config, AWSTemplateDTOConfig)
+ assert dto.provider_config.fleet_type == "request"
+
+ def test_metadata_stays_clean_of_aws_fields(self):
+ """metadata dict must not contain any AWS-specific keys after from_domain."""
+ aws_only_keys = {
+ "fleet_type",
+ "fleet_role",
+ "percent_on_demand",
+ "abis_instance_requirements",
+ }
+ template = _make_aws_template(
+ fleet_type=AWSFleetType.MAINTAIN,
+ fleet_role="arn:aws:iam::123:role/R",
+ percent_on_demand=50,
+ )
+ dto = TemplateDTO.from_domain(template)
+ leaked = aws_only_keys & set(dto.metadata.keys())
+ assert not leaked, f"AWS fields leaked into metadata: {leaked}"
# ---------------------------------------------------------------------------
@@ -147,7 +207,7 @@ def test_existing_metadata_preserved(self):
class TestAWSTemplateRoundTrip:
- """AWSTemplate → TemplateDTO → AWSTemplate must preserve AWS fields."""
+ """AWSTemplate -> TemplateDTO -> AWSTemplate must preserve AWS fields."""
def test_fleet_type_maintain_roundtrip(self):
original = _make_aws_template(fleet_type=AWSFleetType.MAINTAIN)
@@ -174,6 +234,12 @@ def test_percent_on_demand_roundtrip(self):
restored = AWSTemplate.model_validate(dto.model_dump())
assert restored.percent_on_demand == 60
+ def test_launch_template_id_roundtrip(self):
+ original = _make_aws_template(launch_template_id="lt-0deadbeef")
+ dto = TemplateDTO.from_domain(original)
+ restored = AWSTemplate.model_validate(dto.model_dump())
+ assert restored.launch_template_id == "lt-0deadbeef"
+
def test_abis_roundtrip(self):
abis = ABISInstanceRequirements(
VCpuCount=AWSRequiredIntegerRange(Min=4, Max=16),
diff --git a/tests/unit/interface/test_infrastructure_command_handler.py b/tests/unit/interface/test_infrastructure_command_handler.py
index 1fc00d25b..677662a8d 100644
--- a/tests/unit/interface/test_infrastructure_command_handler.py
+++ b/tests/unit/interface/test_infrastructure_command_handler.py
@@ -279,3 +279,170 @@ async def test_unknown_provider_returns_error(self, tmp_path):
result = await handle_infrastructure_validate(args)
assert result["status"] == "error"
+
+
+# ---------------------------------------------------------------------------
+# _get_active_providers — no "aws" hardcoded fallback
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+class TestGetActiveProvidersRegistryFallback:
+ def test_no_config_file_uses_registry_first_type(self, tmp_path):
+ """When no config.json exists, use first registered type (not hardcoded 'aws')."""
+ from unittest.mock import MagicMock, patch
+
+ from orb.interface.infrastructure_command_handler import _get_active_providers
+
+ mock_registry_service = MagicMock()
+ mock_registry_service.get_registered_provider_types.return_value = ["gcp"]
+ mock_container = MagicMock()
+ mock_container.get.return_value = mock_registry_service
+
+ with (
+ patch(
+ "orb.interface.infrastructure_command_handler.get_config_location",
+ return_value=tmp_path,
+ ),
+ patch(
+ "orb.interface.infrastructure_command_handler.get_container",
+ return_value=mock_container,
+ ),
+ ):
+ providers = _get_active_providers()
+
+ assert providers[0]["type"] == "gcp"
+
+ def test_no_config_file_no_registered_types_raises(self, tmp_path):
+ """When no config.json and no registered types, raise RuntimeError."""
+ from unittest.mock import MagicMock, patch
+
+ from orb.interface.infrastructure_command_handler import _get_active_providers
+
+ mock_registry_service = MagicMock()
+ mock_registry_service.get_registered_provider_types.return_value = []
+ mock_container = MagicMock()
+ mock_container.get.return_value = mock_registry_service
+
+ with (
+ patch(
+ "orb.interface.infrastructure_command_handler.get_config_location",
+ return_value=tmp_path,
+ ),
+ patch(
+ "orb.interface.infrastructure_command_handler.get_container",
+ return_value=mock_container,
+ ),
+ ):
+ with pytest.raises(RuntimeError, match="No providers are registered"):
+ _get_active_providers()
+
+ def test_all_providers_disabled_no_registered_types_raises(self, tmp_path):
+ """When all providers disabled and no registered types, raise RuntimeError."""
+ from unittest.mock import MagicMock, patch
+
+ from orb.interface.infrastructure_command_handler import _get_active_providers
+
+ _write_config(
+ tmp_path,
+ {
+ "provider": {
+ "providers": [
+ {
+ "name": "aws-a",
+ "type": "aws",
+ "enabled": False,
+ }
+ ]
+ }
+ },
+ )
+
+ mock_registry_service = MagicMock()
+ mock_registry_service.get_registered_provider_types.return_value = []
+ mock_container = MagicMock()
+ mock_container.get.return_value = mock_registry_service
+
+ with (
+ patch(
+ "orb.interface.infrastructure_command_handler.get_config_location",
+ return_value=tmp_path,
+ ),
+ patch(
+ "orb.interface.infrastructure_command_handler.get_container",
+ return_value=mock_container,
+ ),
+ ):
+ with pytest.raises(RuntimeError, match="No providers are registered"):
+ _get_active_providers()
+
+
+# ---------------------------------------------------------------------------
+# _show_provider_infrastructure — CLISpecRegistry-driven display
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+class TestShowProviderInfrastructure:
+ def test_known_provider_type_uses_spec_format_display(self):
+ """Known provider types use CLISpecRegistry.format_display for display."""
+ from unittest.mock import MagicMock, patch
+
+ from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
+ from orb.interface.infrastructure_command_handler import _show_provider_infrastructure
+
+ mock_spec = MagicMock()
+ mock_spec.format_display.return_value = [("Zone", "us-central1-a"), ("Project", "my-proj")]
+ mock_console = MagicMock()
+ mock_container = MagicMock()
+ mock_container.get.return_value = mock_console
+
+ provider = {
+ "name": "gcp-main",
+ "type": "gcp",
+ "config": {"zone": "us-central1-a", "project": "my-proj"},
+ }
+
+ with (
+ patch.object(CLISpecRegistry, "get", return_value=mock_spec),
+ patch(
+ "orb.interface.infrastructure_command_handler.get_container",
+ return_value=mock_container,
+ ),
+ ):
+ _show_provider_infrastructure(provider)
+
+ mock_spec.format_display.assert_called_once_with(provider["config"])
+ # Ensure format_display output was written to console
+ info_calls = [call.args[0] for call in mock_console.info.call_args_list]
+ assert any("Zone" in c for c in info_calls)
+ assert any("us-central1-a" in c for c in info_calls)
+
+ def test_unknown_provider_type_falls_back_to_raw_config(self):
+ """Unknown provider types fall back to raw key/value display."""
+ from unittest.mock import MagicMock, patch
+
+ from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
+ from orb.interface.infrastructure_command_handler import _show_provider_infrastructure
+
+ mock_console = MagicMock()
+ mock_container = MagicMock()
+ mock_container.get.return_value = mock_console
+
+ provider = {
+ "name": "unknown-1",
+ "type": "unknown",
+ "config": {"endpoint": "https://example.com"},
+ }
+
+ with (
+ patch.object(CLISpecRegistry, "get", return_value=None),
+ patch(
+ "orb.interface.infrastructure_command_handler.get_container",
+ return_value=mock_container,
+ ),
+ ):
+ _show_provider_infrastructure(provider)
+
+ info_calls = [call.args[0] for call in mock_console.info.call_args_list]
+ assert any("https://example.com" in c for c in info_calls)
diff --git a/tests/unit/interface/test_provider_config_handler.py b/tests/unit/interface/test_provider_config_handler.py
index 8c2708d9e..8b3225097 100644
--- a/tests/unit/interface/test_provider_config_handler.py
+++ b/tests/unit/interface/test_provider_config_handler.py
@@ -18,7 +18,7 @@
@pytest.fixture(autouse=True)
def register_aws_cli_spec():
"""Register AWS CLI spec for all tests in this module."""
- from orb.domain.base.ports.provider_cli_spec_port import CLISpecRegistry
+ from orb.infrastructure.registry.cli_spec_registry import CLISpecRegistry
from orb.providers.aws.cli.aws_cli_spec import AWSCLISpec
CLISpecRegistry.register("aws", AWSCLISpec())
@@ -216,6 +216,22 @@ async def test_credential_failure_returns_1(self, tmp_path):
assert result.get("error") is True and result.get("exit_code") == 1
+ @pytest.mark.asyncio
+ async def test_missing_provider_type_returns_1(self, tmp_path):
+ """No provider_type attribute on args must return an error, not default to 'aws'."""
+ from orb.interface.provider_config_handler import handle_provider_add
+
+ _write_config(tmp_path, _base_config())
+ with patch(
+ "orb.interface.provider_config_handler.get_config_location",
+ return_value=tmp_path,
+ ):
+ # args has no provider_type attribute at all
+ args = _ns(aws_profile="default", aws_region="us-east-1", name=None, discover=False)
+ result = await handle_provider_add(args)
+
+ assert result.get("error") is True and result.get("exit_code") == 1
+
# ---------------------------------------------------------------------------
# handle_provider_remove
diff --git a/tests/unit/providers/aws/infrastructure/handlers/test_ec2_fleet_handler.py b/tests/unit/providers/aws/infrastructure/handlers/test_ec2_fleet_handler.py
deleted file mode 100644
index 657182abf..000000000
--- a/tests/unit/providers/aws/infrastructure/handlers/test_ec2_fleet_handler.py
+++ /dev/null
@@ -1,252 +0,0 @@
-"""Unit tests for EC2FleetHandler.check_hosts_status."""
-
-from unittest.mock import MagicMock, patch
-
-import pytest
-from botocore.exceptions import ClientError
-
-from orb.providers.aws.exceptions.aws_exceptions import AWSInfrastructureError
-from orb.providers.aws.infrastructure.handlers.ec2_fleet.handler import EC2FleetHandler
-
-
-def _make_handler():
- aws_client = MagicMock()
- logger = MagicMock()
- aws_ops = MagicMock()
- launch_template_manager = MagicMock()
- handler = EC2FleetHandler(aws_client, logger, aws_ops, launch_template_manager)
- handler._machine_adapter = None
- return handler
-
-
-def _make_request(resource_ids, metadata=None):
- request = MagicMock()
- request.request_id = "req-ec2fleet-123"
- request.resource_ids = resource_ids
- request.metadata = metadata or {}
- return request
-
-
-def _make_client_error(code="InternalError"):
- return ClientError({"Error": {"Code": code, "Message": "boom"}}, "DescribeFleets")
-
-
-def _formatted_instances(instance_ids, resource_id="fleet-test"):
- """Return already-formatted instance dicts (as _check_single_fleet_status returns them)."""
- return [
- {
- "instance_id": iid,
- "resource_id": resource_id,
- "status": "running",
- "private_ip": f"10.0.0.{i}",
- "public_ip": None,
- "launch_time": None,
- "instance_type": "t3.medium",
- "image_id": "ami-123",
- "subnet_id": None,
- "security_group_ids": [],
- "vpc_id": None,
- }
- for i, iid in enumerate(instance_ids)
- ]
-
-
-class TestEC2FleetHandlerCheckHostsStatus:
- def test_check_hosts_status_all_running(self):
- """All instances running → returns all as active."""
- handler = _make_handler()
- request = _make_request(["fleet-111"], metadata={"fleet_type": "maintain"})
- instance_ids = ["i-aaa", "i-bbb"]
-
- with patch.object(
- handler,
- "_check_single_fleet_status",
- return_value=_formatted_instances(instance_ids, "fleet-111"),
- ):
- result = handler.check_hosts_status(request)
-
- assert len(result) == 2
- returned_ids = {r["instance_id"] for r in result}
- assert returned_ids == set(instance_ids)
-
- def test_check_hosts_status_mixed_states(self):
- """describe_fleet_instances only returns active instances — AWS filters for us."""
- handler = _make_handler()
- request = _make_request(["fleet-222"], metadata={"fleet_type": "maintain"})
- active_ids = ["i-running1"]
-
- with patch.object(
- handler,
- "_check_single_fleet_status",
- return_value=_formatted_instances(active_ids, "fleet-222"),
- ):
- result = handler.check_hosts_status(request)
-
- assert len(result) == 1
- assert result[0]["instance_id"] == "i-running1"
-
- def test_check_hosts_status_fleet_not_found(self):
- """_check_single_fleet_status raises → per-fleet exception logged; result is []."""
- handler = _make_handler()
- request = _make_request(["fleet-missing"], metadata={"fleet_type": "maintain"})
-
- with patch.object(
- handler,
- "_check_single_fleet_status",
- side_effect=AWSInfrastructureError("Fleet not found"),
- ):
- result = handler.check_hosts_status(request)
-
- assert result == []
-
- def test_check_hosts_status_multiple_resource_ids(self):
- """Request has 2 fleet IDs → checks both, aggregates results."""
- handler = _make_handler()
- request = _make_request(["fleet-A", "fleet-B"], metadata={"fleet_type": "maintain"})
-
- ids_a = ["i-a1", "i-a2"]
- ids_b = ["i-b1"]
-
- def single_fleet_side_effect(fleet_id, req):
- if fleet_id == "fleet-A":
- return _formatted_instances(ids_a, "fleet-A")
- return _formatted_instances(ids_b, "fleet-B")
-
- with patch.object(
- handler, "_check_single_fleet_status", side_effect=single_fleet_side_effect
- ):
- result = handler.check_hosts_status(request)
-
- assert len(result) == 3
- returned_ids = {r["instance_id"] for r in result}
- assert returned_ids == {"i-a1", "i-a2", "i-b1"}
-
- def test_check_hosts_status_aws_error(self):
- """ClientError inside per-fleet loop → logged and skipped; result is []."""
- handler = _make_handler()
- request = _make_request(["fleet-err"], metadata={"fleet_type": "maintain"})
-
- with patch.object(
- handler, "_check_single_fleet_status", side_effect=_make_client_error("InternalError")
- ):
- result = handler.check_hosts_status(request)
-
- assert result == []
-
- def test_check_hosts_status_no_resource_ids(self):
- """Empty resource_ids → raises AWSInfrastructureError immediately."""
- handler = _make_handler()
- request = _make_request([])
-
- with pytest.raises(AWSInfrastructureError):
- handler.check_hosts_status(request)
-
- def test_check_hosts_status_returns_correct_count(self):
- """Verify count matches active instances returned."""
- handler = _make_handler()
- request = _make_request(["fleet-cnt"], metadata={"fleet_type": "maintain"})
- instance_ids = ["i-1", "i-2", "i-3"]
-
- with patch.object(
- handler,
- "_check_single_fleet_status",
- return_value=_formatted_instances(instance_ids, "fleet-cnt"),
- ):
- result = handler.check_hosts_status(request)
-
- assert len(result) == 3
-
- def test_check_hosts_status_preserves_instance_ids(self):
- """Instance IDs in result match input."""
- handler = _make_handler()
- request = _make_request(["fleet-ids"], metadata={"fleet_type": "maintain"})
- instance_ids = ["i-preserve1", "i-preserve2"]
-
- with patch.object(
- handler,
- "_check_single_fleet_status",
- return_value=_formatted_instances(instance_ids, "fleet-ids"),
- ):
- result = handler.check_hosts_status(request)
-
- returned_ids = {r["instance_id"] for r in result}
- assert returned_ids == set(instance_ids)
-
- def test_check_hosts_status_state_filtering_is_strict(self):
- """Only instances returned by _check_single_fleet_status appear in result."""
- handler = _make_handler()
- request = _make_request(["fleet-strict"], metadata={"fleet_type": "maintain"})
- active_ids = ["i-strict-active"]
-
- with patch.object(
- handler,
- "_check_single_fleet_status",
- return_value=_formatted_instances(active_ids, "fleet-strict"),
- ):
- result = handler.check_hosts_status(request)
-
- assert len(result) == 1
- assert result[0]["instance_id"] == "i-strict-active"
-
- def test_check_hosts_status_instant_fleet_no_active_instances(self):
- """Instant fleet with no instances → _check_single_fleet_status returns []."""
- handler = _make_handler()
- request = _make_request(
- ["fleet-instant"],
- metadata={"fleet_type": "instant", "instance_ids": []},
- )
-
- with patch.object(handler, "_check_single_fleet_status", return_value=[]):
- result = handler.check_hosts_status(request)
-
- assert result == []
-
-
-class TestEC2FleetHandlerNameTag:
- def test_fleet_config_instance_tag_uses_config_prefix(self):
- """Instance Name tag in EC2Fleet config uses config_port prefix (instant fleet only)."""
- from orb.providers.aws.domain.template.aws_template_aggregate import AWSFleetType
-
- aws_client = MagicMock()
- logger = MagicMock()
- aws_ops = MagicMock()
- launch_template_manager = MagicMock()
- config_port = MagicMock()
- config_port.get_resource_prefix.side_effect = lambda rt: (
- "pfx-" if rt == "fleet" else "inst-"
- )
-
- handler = EC2FleetHandler(
- aws_client, logger, aws_ops, launch_template_manager, config_port=config_port
- )
-
- template = MagicMock()
- template.fleet_type = AWSFleetType.INSTANT
- template.tags = {}
- template.price_type = "ondemand"
- template.allocation_strategy = None
- template.max_price = None
- template.machine_types = {"m5.large": 1}
- template.subnet_ids = ["subnet-abc"]
- template.template_id = "tmpl-ec2"
- template.percent_on_demand = None
- template.context = None
-
- request = MagicMock()
- request.request_id = "req-ec2-001"
- request.requested_count = 2
-
- fleet_config = handler._create_fleet_config(template, request, "lt-xyz", "$Default")
-
- instance_ts = next(
- (
- ts
- for ts in fleet_config.get("TagSpecifications", [])
- if ts["ResourceType"] == "instance"
- ),
- None,
- )
- assert instance_ts is not None, "No instance TagSpecification found (instant fleet)"
- name_tag = next((t for t in instance_ts["Tags"] if t["Key"] == "Name"), None)
- assert name_tag is not None, "No Name tag in instance TagSpecification"
- assert "req-ec2-001" in name_tag["Value"]
diff --git a/tests/unit/providers/test_provider_registry.py b/tests/unit/providers/test_provider_registry.py
index 5685ac5e6..a0235fc3e 100644
--- a/tests/unit/providers/test_provider_registry.py
+++ b/tests/unit/providers/test_provider_registry.py
@@ -1,7 +1,7 @@
-"""Tests for ProviderRegistry._provider_supports_api — no hardcoded AWS API list."""
+"""Tests for ProviderRegistry — no hardcoded AWS API list and provider_type allowlist."""
from typing import cast
-from unittest.mock import MagicMock
+from unittest.mock import MagicMock, patch
import pytest
@@ -160,3 +160,123 @@ def test_capabilities_instance_attr_still_takes_precedence(self):
registry = _make_registry(provider, strategy=strategy)
assert registry._provider_supports_api(provider, "SpotFleet") is True
+
+
+# ---------------------------------------------------------------------------
+# Provider type allowlist — ensure_provider_type_registered and
+# ensure_provider_instance_registered_from_config must reject provider_type
+# values that would allow module-injection via importlib.import_module.
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.unit
+class TestProviderTypeAllowlist:
+ """ensure_provider_type_registered must reject non-alphanumeric provider types."""
+
+ def _make_bare_registry(self) -> ProviderRegistry:
+ registry = cast(ProviderRegistry, ProviderRegistry.__new__(ProviderRegistry))
+ registry._type_registrations = {}
+ registry._instance_registrations = {}
+ registry._registry_lock = __import__("threading").RLock()
+ registry.mode = __import__(
+ "orb.infrastructure.registry.base_registry", fromlist=["RegistryMode"]
+ ).RegistryMode.MULTI_CHOICE
+ registry._factory = None
+ registry._initialized = True
+ registry._strategy_cache = {}
+ registry._health_states = {}
+ registry._fallback_strategy = None
+ registry._config_port = None
+ registry._logger = MagicMock()
+ return registry
+
+ # --- ensure_provider_type_registered ---
+
+ def test_valid_provider_type_attempts_import(self):
+ """A valid snake_case type like 'aws' attempts the dynamic import (no ValueError)."""
+ registry = self._make_bare_registry()
+
+ with patch("importlib.import_module", side_effect=ImportError("no module")) as mock_import:
+ result = registry.ensure_provider_type_registered("aws")
+
+ # ImportError → returns False, but the import WAS attempted (no ValueError raised)
+ mock_import.assert_called_once_with("orb.providers.aws.registration")
+ assert result is False
+
+ def test_valid_provider_type_with_underscores(self):
+ """Types like 'my_provider' pass the allowlist."""
+ registry = self._make_bare_registry()
+
+ with patch("importlib.import_module", side_effect=ImportError("no module")):
+ # Should not raise ValueError
+ registry.ensure_provider_type_registered("my_provider")
+
+ def test_valid_provider_type_with_digits(self):
+ """Types like 'provider1' pass the allowlist."""
+ registry = self._make_bare_registry()
+
+ with patch("importlib.import_module", side_effect=ImportError("no module")):
+ registry.ensure_provider_type_registered("provider1")
+
+ def test_dot_in_provider_type_raises_value_error(self):
+ """A provider_type containing a dot (e.g. 'os.path') must raise ValueError."""
+ registry = self._make_bare_registry()
+
+ with pytest.raises(ValueError, match="Invalid provider type"):
+ registry.ensure_provider_type_registered("os.path")
+
+ def test_path_traversal_raises_value_error(self):
+ """A path-traversal string must raise ValueError."""
+ registry = self._make_bare_registry()
+
+ with pytest.raises(ValueError, match="Invalid provider type"):
+ registry.ensure_provider_type_registered("../../etc/passwd")
+
+ def test_uppercase_raises_value_error(self):
+ """Uppercase letters are not permitted (consistent snake_case convention)."""
+ registry = self._make_bare_registry()
+
+ with pytest.raises(ValueError, match="Invalid provider type"):
+ registry.ensure_provider_type_registered("AWS")
+
+ def test_leading_digit_raises_value_error(self):
+ """A provider_type starting with a digit must raise ValueError."""
+ registry = self._make_bare_registry()
+
+ with pytest.raises(ValueError, match="Invalid provider type"):
+ registry.ensure_provider_type_registered("1provider")
+
+ def test_space_in_provider_type_raises_value_error(self):
+ """A provider_type with whitespace must raise ValueError."""
+ registry = self._make_bare_registry()
+
+ with pytest.raises(ValueError, match="Invalid provider type"):
+ registry.ensure_provider_type_registered("aws provider")
+
+ # --- ensure_provider_instance_registered_from_config ---
+
+ def test_instance_registration_dot_in_type_raises_value_error(self):
+ """ensure_provider_instance_registered_from_config rejects dotted provider types."""
+ registry = self._make_bare_registry()
+
+ provider_instance = MagicMock()
+ provider_instance.name = "my-instance"
+ provider_instance.type = "malicious.module"
+ # Not yet registered
+ registry._instance_registrations = {}
+
+ with pytest.raises(ValueError, match="Invalid provider type"):
+ registry.ensure_provider_instance_registered_from_config(provider_instance)
+
+ def test_instance_registration_valid_type_attempts_import(self):
+ """ensure_provider_instance_registered_from_config passes validation for 'aws'."""
+ registry = self._make_bare_registry()
+
+ provider_instance = MagicMock()
+ provider_instance.name = "aws-east"
+ provider_instance.type = "aws"
+
+ with patch("importlib.import_module", side_effect=ImportError("no module")):
+ result = registry.ensure_provider_instance_registered_from_config(provider_instance)
+
+ assert result is False # ImportError → False, but no ValueError
diff --git a/tests/unit/providers/test_scenarios_helpers.py b/tests/unit/providers/test_scenarios_helpers.py
new file mode 100644
index 000000000..d86d0ac9e
--- /dev/null
+++ b/tests/unit/providers/test_scenarios_helpers.py
@@ -0,0 +1,60 @@
+"""Smoke tests for helper functions in tests/providers/aws/live/scenarios.py."""
+
+from tests.providers.aws.live.scenarios import _load_template_vm_types
+
+
+class TestLoadTemplateVmTypes:
+ def test_known_template_returns_nonempty_dict(self) -> None:
+ """EC2Fleet-Maintain-OnDemand exists in config/aws_templates.json and has vmTypes."""
+ result = _load_template_vm_types("EC2Fleet-Maintain-OnDemand")
+ assert isinstance(result, dict), "Expected a dict"
+ assert result, "Expected a non-empty dict for EC2Fleet-Maintain-OnDemand"
+
+ def test_unknown_template_returns_empty_dict(self) -> None:
+ """An unrecognised template ID should silently return {}."""
+ result = _load_template_vm_types("DoesNotExist-Template-ID")
+ assert result == {}
+
+ def test_file_not_found_returns_empty_dict(self, monkeypatch) -> None:
+ """FileNotFoundError during open() is swallowed and returns {}."""
+ import builtins
+ from pathlib import Path
+
+ def fake_open(path, *args, **kwargs):
+ raise FileNotFoundError(f"injected: {path}")
+
+ monkeypatch.setattr(builtins, "open", fake_open)
+ # templates_path.exists() must return True to reach the open() call
+ monkeypatch.setattr(Path, "exists", lambda self: True)
+ result = _load_template_vm_types("EC2Fleet-Maintain-OnDemand")
+ assert result == {}
+
+ def test_json_decode_error_returns_empty_dict(self, monkeypatch) -> None:
+ """json.JSONDecodeError during json.load() is swallowed and returns {}."""
+ import builtins
+ import io
+ from pathlib import Path
+
+ def fake_open(path, *args, **kwargs):
+ return io.StringIO("<<>>")
+
+ monkeypatch.setattr(builtins, "open", fake_open)
+ monkeypatch.setattr(Path, "exists", lambda self: True)
+ result = _load_template_vm_types("EC2Fleet-Maintain-OnDemand")
+ assert result == {}
+
+ def test_unexpected_exception_is_reraised(self, monkeypatch) -> None:
+ """Unexpected exceptions (not FileNotFoundError/JSONDecodeError) are re-raised."""
+ import builtins
+
+ def exploding_open(path, *args, **kwargs):
+ raise RuntimeError("disk on fire")
+
+ monkeypatch.setattr(builtins, "open", exploding_open)
+ from pathlib import Path
+
+ monkeypatch.setattr(Path, "exists", lambda self: True)
+ import pytest
+
+ with pytest.raises(RuntimeError, match="disk on fire"):
+ _load_template_vm_types("EC2Fleet-Maintain-OnDemand")
diff --git a/tests/unit/sdk/test_sdk_config.py b/tests/unit/sdk/test_sdk_config.py
index 838000288..7827a778a 100644
--- a/tests/unit/sdk/test_sdk_config.py
+++ b/tests/unit/sdk/test_sdk_config.py
@@ -102,6 +102,30 @@ def test_empty_dict_uses_defaults(self):
config = SDKConfig.from_dict({})
assert config.provider == "aws"
+ def test_scheduler_dict_not_absorbed_as_string_override(self):
+ # Regression: when loading from an ORB config.json the top-level "scheduler" key
+ # is a nested config dict {"type": "hostfactory", "config_root": "..."}.
+ # from_dict must NOT ingest that dict as the SDKConfig.scheduler string override
+ # because that propagates a dict into ConfigurationManager.override_scheduler_strategy
+ # which then surfaces as "unhashable type: 'dict'" deep in the DI factory chain.
+ orb_config = {
+ "provider": {"type": "aws"},
+ "scheduler": {"type": "hostfactory", "config_root": "$ORB_CONFIG_DIR"},
+ }
+ config = SDKConfig.from_dict(orb_config)
+ # scheduler field must remain None (no string override set)
+ assert config.scheduler is None
+ # the original dict is preserved in custom_config so nothing is silently dropped
+ assert config.custom_config.get("scheduler") == {
+ "type": "hostfactory",
+ "config_root": "$ORB_CONFIG_DIR",
+ }
+
+ def test_scheduler_string_override_is_still_accepted(self):
+ # Explicit string scheduler overrides (e.g. passed programmatically) must still work.
+ config = SDKConfig.from_dict({"provider": "aws", "scheduler": "default"})
+ assert config.scheduler == "default"
+
class TestSDKConfigFromFile:
def test_loads_json_file(self, tmp_path):
diff --git a/tests/unit/test_health_no_aws.py b/tests/unit/test_health_no_aws.py
index d5ac6fa32..c35a0c63a 100644
--- a/tests/unit/test_health_no_aws.py
+++ b/tests/unit/test_health_no_aws.py
@@ -40,11 +40,11 @@ def test_register_aws_health_checks_callable():
)
-# --- task 1719: register_aws_health_checks registers checks on HealthCheck ---
+# --- register_aws_health_checks: conditional DynamoDB registration ---
-def test_register_aws_health_checks_registers_checks():
- """register_aws_health_checks must add aws, ec2, and dynamodb checks."""
+def test_register_aws_health_checks_always_registers_aws_and_ec2():
+ """register_aws_health_checks must always add aws and ec2 checks."""
from unittest.mock import MagicMock
from orb.providers.aws.health import register_aws_health_checks
@@ -52,10 +52,44 @@ def test_register_aws_health_checks_registers_checks():
health_check = MagicMock()
aws_client = MagicMock()
- register_aws_health_checks(health_check, aws_client)
+ register_aws_health_checks(health_check, aws_client, storage_strategy="json")
+
+ registered_names = {call.args[0] for call in health_check.register_check.call_args_list}
+ assert "aws" in registered_names
+ assert "ec2" in registered_names
+ assert "dynamodb" not in registered_names
+ assert health_check.register_check.call_count == 2
+
+
+def test_register_aws_health_checks_registers_dynamodb_when_dynamodb_storage():
+ """register_aws_health_checks must add dynamodb check only when storage_strategy='dynamodb'."""
+ from unittest.mock import MagicMock
+
+ from orb.providers.aws.health import register_aws_health_checks
+
+ health_check = MagicMock()
+ aws_client = MagicMock()
+
+ register_aws_health_checks(health_check, aws_client, storage_strategy="dynamodb")
registered_names = {call.args[0] for call in health_check.register_check.call_args_list}
assert "aws" in registered_names
assert "ec2" in registered_names
assert "dynamodb" in registered_names
assert health_check.register_check.call_count == 3
+
+
+def test_register_aws_health_checks_skips_dynamodb_for_sql_storage():
+ """register_aws_health_checks must not add dynamodb check when storage_strategy='sql'."""
+ from unittest.mock import MagicMock
+
+ from orb.providers.aws.health import register_aws_health_checks
+
+ health_check = MagicMock()
+ aws_client = MagicMock()
+
+ register_aws_health_checks(health_check, aws_client, storage_strategy="sql")
+
+ registered_names = {call.args[0] for call in health_check.register_check.call_args_list}
+ assert "dynamodb" not in registered_names
+ assert health_check.register_check.call_count == 2
diff --git a/tests/unit/test_no_provider_install.py b/tests/unit/test_no_provider_install.py
new file mode 100644
index 000000000..da37ff7f8
--- /dev/null
+++ b/tests/unit/test_no_provider_install.py
@@ -0,0 +1,161 @@
+"""Verify ORB core boots cleanly when the [aws] extra is not installed.
+
+These tests use subprocess + import mocking to simulate a boto3-free environment
+without requiring a separate virtualenv. The approach patches sys.modules to make
+boto3/botocore appear absent, then imports ORB core modules under that constraint.
+
+Marked @pytest.mark.slow because they manipulate sys.modules and must restore state.
+"""
+
+from __future__ import annotations
+
+import importlib
+import sys
+from types import ModuleType
+from unittest.mock import patch
+
+import pytest
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+class _ImportBlocker(ModuleType):
+ """A fake module that raises ImportError on attribute access, simulating absence."""
+
+ def __getattr__(self, name: str) -> object:
+ raise ImportError("boto3 extra not installed (simulated)")
+
+
+def _block_modules(*names: str) -> dict[str, ModuleType | None]:
+ """Return a dict suitable for patching sys.modules to block *names*."""
+ return {name: None for name in names} # type: ignore[dict-item]
+
+
+# ---------------------------------------------------------------------------
+# Tests
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.slow
+def test_cleanup_schema_import_without_aws() -> None:
+ """cleanup_schema must import without error even when boto3 is absent."""
+ blocked = {
+ "boto3": None,
+ "botocore": None,
+ "orb.providers.aws.configuration.cleanup_config": None,
+ }
+ # Remove cached module if present
+ cached = {k: sys.modules.pop(k, None) for k in blocked}
+ cached.pop("orb.config.schemas.cleanup_schema", None)
+ sys.modules.pop("orb.config.schemas.cleanup_schema", None)
+
+ try:
+ with patch.dict(sys.modules, blocked):
+ import importlib
+
+ mod = importlib.import_module("orb.config.schemas.cleanup_schema")
+ # CleanupConfig should be None when AWS extra is absent
+ assert mod.CleanupConfig is None, (
+ "CleanupConfig should be None when [aws] extra is absent"
+ )
+ assert mod.CleanupResourcesConfig is None, (
+ "CleanupResourcesConfig should be None when [aws] extra is absent"
+ )
+ finally:
+ # Restore originals so other tests are unaffected
+ for k, v in cached.items():
+ if v is not None:
+ sys.modules[k] = v
+ else:
+ sys.modules.pop(k, None)
+ sys.modules.pop("orb.config.schemas.cleanup_schema", None)
+
+
+@pytest.mark.slow
+def test_storage_registration_without_aws() -> None:
+ """register_all_storage_types must not raise when AWS DynamoDB import fails."""
+ blocked = {
+ "boto3": None,
+ "botocore": None,
+ "orb.providers.aws.storage.registration": None,
+ }
+ cached = {k: sys.modules.pop(k, None) for k in blocked}
+ sys.modules.pop("orb.infrastructure.storage.registration", None)
+
+ try:
+ with patch.dict(sys.modules, blocked):
+ mod = importlib.import_module("orb.infrastructure.storage.registration")
+ # Should not raise — DynamoDB registration is silently skipped
+ try:
+ mod.register_all_storage_types()
+ except ImportError as exc:
+ pytest.fail(
+ f"register_all_storage_types() raised ImportError when [aws] absent: {exc}"
+ )
+ finally:
+ for k, v in cached.items():
+ if v is not None:
+ sys.modules[k] = v
+ else:
+ sys.modules.pop(k, None)
+ sys.modules.pop("orb.infrastructure.storage.registration", None)
+
+
+@pytest.mark.slow
+def test_provider_cli_specs_without_aws() -> None:
+ """register_all_provider_cli_specs must not raise when [aws] is absent."""
+ blocked = {
+ "boto3": None,
+ "botocore": None,
+ "orb.providers.aws.cli.aws_cli_spec": None,
+ }
+ cached = {k: sys.modules.pop(k, None) for k in blocked}
+ sys.modules.pop("orb.providers.registration", None)
+
+ try:
+ with patch.dict(sys.modules, blocked):
+ mod = importlib.import_module("orb.providers.registration")
+ try:
+ mod.register_all_provider_cli_specs()
+ except ImportError as exc:
+ pytest.fail(
+ f"register_all_provider_cli_specs() raised ImportError when [aws] absent: {exc}"
+ )
+ finally:
+ for k, v in cached.items():
+ if v is not None:
+ sys.modules[k] = v
+ else:
+ sys.modules.pop(k, None)
+ sys.modules.pop("orb.providers.registration", None)
+
+
+@pytest.mark.slow
+def test_provider_defaults_loaders_without_aws() -> None:
+ """register_all_defaults_loaders must not raise when [aws] is absent."""
+ blocked = {
+ "boto3": None,
+ "botocore": None,
+ "orb.providers.aws.defaults_loader": None,
+ }
+ cached = {k: sys.modules.pop(k, None) for k in blocked}
+ sys.modules.pop("orb.providers.registration", None)
+
+ try:
+ with patch.dict(sys.modules, blocked):
+ mod = importlib.import_module("orb.providers.registration")
+ try:
+ mod.register_all_defaults_loaders()
+ except ImportError as exc:
+ pytest.fail(
+ f"register_all_defaults_loaders() raised ImportError when [aws] absent: {exc}"
+ )
+ finally:
+ for k, v in cached.items():
+ if v is not None:
+ sys.modules[k] = v
+ else:
+ sys.modules.pop(k, None)
+ sys.modules.pop("orb.providers.registration", None)
diff --git a/uv.lock b/uv.lock
index 998504f9a..5cec048f7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -10,289 +10,289 @@ resolution-markers = [
[[package]]
name = "annotated-doc"
version = "0.0.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "antlr4-python3-runtime"
version = "4.13.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467, upload-time = "2024-08-03T19:00:12.757Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467, upload-time = "2024-08-03T19:00:12.757Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462, upload-time = "2024-08-03T19:00:11.134Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462, upload-time = "2024-08-03T19:00:11.134Z" },
]
[[package]]
name = "anyio"
version = "4.14.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "idna" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" },
]
[[package]]
name = "arrow"
version = "1.4.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "python-dateutil" },
{ name = "tzdata" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
]
[[package]]
name = "asgiref"
version = "3.11.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" },
]
[[package]]
name = "asttokens"
version = "3.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" },
]
[[package]]
name = "attrs"
version = "26.1.0"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
]
[[package]]
name = "authlib"
version = "1.7.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "cryptography" },
{ name = "joserfc" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" },
]
[[package]]
name = "aws-sam-translator"
version = "1.110.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "boto3" },
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6e/2f/adeed2ce2bc62eca7ead7b3ae70fdd2cf84eecd582cd69a9529e6da89876/aws_sam_translator-1.110.0.tar.gz", hash = "sha256:466ee0e8200992c51b7fd5ede5e56ca2e8dd5473cc551e8495c14f2f4d636127", size = 368671, upload-time = "2026-05-19T21:21:06.959Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6e/2f/adeed2ce2bc62eca7ead7b3ae70fdd2cf84eecd582cd69a9529e6da89876/aws_sam_translator-1.110.0.tar.gz", hash = "sha256:466ee0e8200992c51b7fd5ede5e56ca2e8dd5473cc551e8495c14f2f4d636127", size = 368671, upload-time = "2026-05-19T21:21:06.959Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/42/6f/286e3e49d3b6b181473fefa5d9fc02e10d98ccc417e0de74e396db951fd9/aws_sam_translator-1.110.0-py3-none-any.whl", hash = "sha256:69b09aacf2d305ac747037b7b913224cb8a9d653f47a0306509c1d20e420b670", size = 431671, upload-time = "2026-05-19T21:21:05.26Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/6f/286e3e49d3b6b181473fefa5d9fc02e10d98ccc417e0de74e396db951fd9/aws_sam_translator-1.110.0-py3-none-any.whl", hash = "sha256:69b09aacf2d305ac747037b7b913224cb8a9d653f47a0306509c1d20e420b670", size = 431671, upload-time = "2026-05-19T21:21:05.26Z" },
]
[[package]]
name = "aws-xray-sdk"
version = "2.15.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "botocore" },
{ name = "wrapt" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/14/25/0cbd7a440080def5e6f063720c3b190a25f8aa2938c1e34415dc18241596/aws_xray_sdk-2.15.0.tar.gz", hash = "sha256:794381b96e835314345068ae1dd3b9120bd8b4e21295066c37e8814dbb341365", size = 76315, upload-time = "2025-10-29T20:59:45Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/14/25/0cbd7a440080def5e6f063720c3b190a25f8aa2938c1e34415dc18241596/aws_xray_sdk-2.15.0.tar.gz", hash = "sha256:794381b96e835314345068ae1dd3b9120bd8b4e21295066c37e8814dbb341365", size = 76315, upload-time = "2025-10-29T20:59:45Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ef/c3/f30a7a63e664acc7c2545ca0491b6ce8264536e0e5cad3965f1d1b91e960/aws_xray_sdk-2.15.0-py2.py3-none-any.whl", hash = "sha256:422d62ad7d52e373eebb90b642eb1bb24657afe03b22a8df4a8b2e5108e278a3", size = 103228, upload-time = "2025-10-29T21:00:24.12Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/c3/f30a7a63e664acc7c2545ca0491b6ce8264536e0e5cad3965f1d1b91e960/aws_xray_sdk-2.15.0-py2.py3-none-any.whl", hash = "sha256:422d62ad7d52e373eebb90b642eb1bb24657afe03b22a8df4a8b2e5108e278a3", size = 103228, upload-time = "2025-10-29T21:00:24.12Z" },
]
[[package]]
name = "babel"
version = "2.18.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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "backports-asyncio-runner"
version = "1.2.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
]
[[package]]
name = "backports-datetime-fromisoformat"
version = "2.0.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/71/81/eff3184acb1d9dc3ce95a98b6f3c81a49b4be296e664db8e1c2eeabef3d9/backports_datetime_fromisoformat-2.0.3.tar.gz", hash = "sha256:b58edc8f517b66b397abc250ecc737969486703a66eb97e01e6d51291b1a139d", size = 23588, upload-time = "2024-12-28T20:18:15.017Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/42/4b/d6b051ca4b3d76f23c2c436a9669f3be616b8cf6461a7e8061c7c4269642/backports_datetime_fromisoformat-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f681f638f10588fa3c101ee9ae2b63d3734713202ddfcfb6ec6cea0778a29d4", size = 27561, upload-time = "2024-12-28T20:16:47.974Z" },
- { url = "https://files.pythonhosted.org/packages/6d/40/e39b0d471e55eb1b5c7c81edab605c02f71c786d59fb875f0a6f23318747/backports_datetime_fromisoformat-2.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:cd681460e9142f1249408e5aee6d178c6d89b49e06d44913c8fdfb6defda8d1c", size = 34448, upload-time = "2024-12-28T20:16:50.712Z" },
- { url = "https://files.pythonhosted.org/packages/f2/28/7a5c87c5561d14f1c9af979231fdf85d8f9fad7a95ff94e56d2205e2520a/backports_datetime_fromisoformat-2.0.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:ee68bc8735ae5058695b76d3bb2aee1d137c052a11c8303f1e966aa23b72b65b", size = 27093, upload-time = "2024-12-28T20:16:52.994Z" },
- { url = "https://files.pythonhosted.org/packages/80/ba/f00296c5c4536967c7d1136107fdb91c48404fe769a4a6fd5ab045629af8/backports_datetime_fromisoformat-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8273fe7932db65d952a43e238318966eab9e49e8dd546550a41df12175cc2be4", size = 52836, upload-time = "2024-12-28T20:16:55.283Z" },
- { url = "https://files.pythonhosted.org/packages/e3/92/bb1da57a069ddd601aee352a87262c7ae93467e66721d5762f59df5021a6/backports_datetime_fromisoformat-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39d57ea50aa5a524bb239688adc1d1d824c31b6094ebd39aa164d6cadb85de22", size = 52798, upload-time = "2024-12-28T20:16:56.64Z" },
- { url = "https://files.pythonhosted.org/packages/df/ef/b6cfd355982e817ccdb8d8d109f720cab6e06f900784b034b30efa8fa832/backports_datetime_fromisoformat-2.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ac6272f87693e78209dc72e84cf9ab58052027733cd0721c55356d3c881791cf", size = 52891, upload-time = "2024-12-28T20:16:58.887Z" },
- { url = "https://files.pythonhosted.org/packages/37/39/b13e3ae8a7c5d88b68a6e9248ffe7066534b0cfe504bf521963e61b6282d/backports_datetime_fromisoformat-2.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:44c497a71f80cd2bcfc26faae8857cf8e79388e3d5fbf79d2354b8c360547d58", size = 52955, upload-time = "2024-12-28T20:17:00.028Z" },
- { url = "https://files.pythonhosted.org/packages/1e/e4/70cffa3ce1eb4f2ff0c0d6f5d56285aacead6bd3879b27a2ba57ab261172/backports_datetime_fromisoformat-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:6335a4c9e8af329cb1ded5ab41a666e1448116161905a94e054f205aa6d263bc", size = 29323, upload-time = "2024-12-28T20:17:01.125Z" },
- { url = "https://files.pythonhosted.org/packages/62/f5/5bc92030deadf34c365d908d4533709341fb05d0082db318774fdf1b2bcb/backports_datetime_fromisoformat-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2e4b66e017253cdbe5a1de49e0eecff3f66cd72bcb1229d7db6e6b1832c0443", size = 27626, upload-time = "2024-12-28T20:17:03.448Z" },
- { url = "https://files.pythonhosted.org/packages/28/45/5885737d51f81dfcd0911dd5c16b510b249d4c4cf6f4a991176e0358a42a/backports_datetime_fromisoformat-2.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:43e2d648e150777e13bbc2549cc960373e37bf65bd8a5d2e0cef40e16e5d8dd0", size = 34588, upload-time = "2024-12-28T20:17:04.459Z" },
- { url = "https://files.pythonhosted.org/packages/bc/6d/bd74de70953f5dd3e768c8fc774af942af0ce9f211e7c38dd478fa7ea910/backports_datetime_fromisoformat-2.0.3-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:4ce6326fd86d5bae37813c7bf1543bae9e4c215ec6f5afe4c518be2635e2e005", size = 27162, upload-time = "2024-12-28T20:17:06.752Z" },
- { url = "https://files.pythonhosted.org/packages/47/ba/1d14b097f13cce45b2b35db9898957578b7fcc984e79af3b35189e0d332f/backports_datetime_fromisoformat-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7c8fac333bf860208fd522a5394369ee3c790d0aa4311f515fcc4b6c5ef8d75", size = 54482, upload-time = "2024-12-28T20:17:08.15Z" },
- { url = "https://files.pythonhosted.org/packages/25/e9/a2a7927d053b6fa148b64b5e13ca741ca254c13edca99d8251e9a8a09cfe/backports_datetime_fromisoformat-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4da5ab3aa0cc293dc0662a0c6d1da1a011dc1edcbc3122a288cfed13a0b45", size = 54362, upload-time = "2024-12-28T20:17:10.605Z" },
- { url = "https://files.pythonhosted.org/packages/c1/99/394fb5e80131a7d58c49b89e78a61733a9994885804a0bb582416dd10c6f/backports_datetime_fromisoformat-2.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58ea11e3bf912bd0a36b0519eae2c5b560b3cb972ea756e66b73fb9be460af01", size = 54162, upload-time = "2024-12-28T20:17:12.301Z" },
- { url = "https://files.pythonhosted.org/packages/88/25/1940369de573c752889646d70b3fe8645e77b9e17984e72a554b9b51ffc4/backports_datetime_fromisoformat-2.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8a375c7dbee4734318714a799b6c697223e4bbb57232af37fbfff88fb48a14c6", size = 54118, upload-time = "2024-12-28T20:17:13.609Z" },
- { url = "https://files.pythonhosted.org/packages/b7/46/f275bf6c61683414acaf42b2df7286d68cfef03e98b45c168323d7707778/backports_datetime_fromisoformat-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:ac677b1664c4585c2e014739f6678137c8336815406052349c85898206ec7061", size = 29329, upload-time = "2024-12-28T20:17:16.124Z" },
- { url = "https://files.pythonhosted.org/packages/a2/0f/69bbdde2e1e57c09b5f01788804c50e68b29890aada999f2b1a40519def9/backports_datetime_fromisoformat-2.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ce47ee1ba91e146149cf40565c3d750ea1be94faf660ca733d8601e0848147", size = 27630, upload-time = "2024-12-28T20:17:19.442Z" },
- { url = "https://files.pythonhosted.org/packages/d5/1d/1c84a50c673c87518b1adfeafcfd149991ed1f7aedc45d6e5eac2f7d19d7/backports_datetime_fromisoformat-2.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:8b7e069910a66b3bba61df35b5f879e5253ff0821a70375b9daf06444d046fa4", size = 34707, upload-time = "2024-12-28T20:17:21.79Z" },
- { url = "https://files.pythonhosted.org/packages/71/44/27eae384e7e045cda83f70b551d04b4a0b294f9822d32dea1cbf1592de59/backports_datetime_fromisoformat-2.0.3-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:a3b5d1d04a9e0f7b15aa1e647c750631a873b298cdd1255687bb68779fe8eb35", size = 27280, upload-time = "2024-12-28T20:17:24.503Z" },
- { url = "https://files.pythonhosted.org/packages/a7/7a/a4075187eb6bbb1ff6beb7229db5f66d1070e6968abeb61e056fa51afa5e/backports_datetime_fromisoformat-2.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1b95986430e789c076610aea704db20874f0781b8624f648ca9fb6ef67c6e1", size = 55094, upload-time = "2024-12-28T20:17:25.546Z" },
- { url = "https://files.pythonhosted.org/packages/71/03/3fced4230c10af14aacadc195fe58e2ced91d011217b450c2e16a09a98c8/backports_datetime_fromisoformat-2.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffe5f793db59e2f1d45ec35a1cf51404fdd69df9f6952a0c87c3060af4c00e32", size = 55605, upload-time = "2024-12-28T20:17:29.208Z" },
- { url = "https://files.pythonhosted.org/packages/f6/0a/4b34a838c57bd16d3e5861ab963845e73a1041034651f7459e9935289cfd/backports_datetime_fromisoformat-2.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:620e8e73bd2595dfff1b4d256a12b67fce90ece3de87b38e1dde46b910f46f4d", size = 55353, upload-time = "2024-12-28T20:17:32.433Z" },
- { url = "https://files.pythonhosted.org/packages/d9/68/07d13c6e98e1cad85606a876367ede2de46af859833a1da12c413c201d78/backports_datetime_fromisoformat-2.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4cf9c0a985d68476c1cabd6385c691201dda2337d7453fb4da9679ce9f23f4e7", size = 55298, upload-time = "2024-12-28T20:17:34.919Z" },
- { url = "https://files.pythonhosted.org/packages/60/33/45b4d5311f42360f9b900dea53ab2bb20a3d61d7f9b7c37ddfcb3962f86f/backports_datetime_fromisoformat-2.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:d144868a73002e6e2e6fef72333e7b0129cecdd121aa8f1edba7107fd067255d", size = 29375, upload-time = "2024-12-28T20:17:36.018Z" },
- { url = "https://files.pythonhosted.org/packages/be/03/7eaa9f9bf290395d57fd30d7f1f2f9dff60c06a31c237dc2beb477e8f899/backports_datetime_fromisoformat-2.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90e202e72a3d5aae673fcc8c9a4267d56b2f532beeb9173361293625fe4d2039", size = 28980, upload-time = "2024-12-28T20:18:06.554Z" },
- { url = "https://files.pythonhosted.org/packages/47/80/a0ecf33446c7349e79f54cc532933780341d20cff0ee12b5bfdcaa47067e/backports_datetime_fromisoformat-2.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2df98ef1b76f5a58bb493dda552259ba60c3a37557d848e039524203951c9f06", size = 28449, upload-time = "2024-12-28T20:18:07.77Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/81/eff3184acb1d9dc3ce95a98b6f3c81a49b4be296e664db8e1c2eeabef3d9/backports_datetime_fromisoformat-2.0.3.tar.gz", hash = "sha256:b58edc8f517b66b397abc250ecc737969486703a66eb97e01e6d51291b1a139d", size = 23588, upload-time = "2024-12-28T20:18:15.017Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/4b/d6b051ca4b3d76f23c2c436a9669f3be616b8cf6461a7e8061c7c4269642/backports_datetime_fromisoformat-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f681f638f10588fa3c101ee9ae2b63d3734713202ddfcfb6ec6cea0778a29d4", size = 27561, upload-time = "2024-12-28T20:16:47.974Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/40/e39b0d471e55eb1b5c7c81edab605c02f71c786d59fb875f0a6f23318747/backports_datetime_fromisoformat-2.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:cd681460e9142f1249408e5aee6d178c6d89b49e06d44913c8fdfb6defda8d1c", size = 34448, upload-time = "2024-12-28T20:16:50.712Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f2/28/7a5c87c5561d14f1c9af979231fdf85d8f9fad7a95ff94e56d2205e2520a/backports_datetime_fromisoformat-2.0.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:ee68bc8735ae5058695b76d3bb2aee1d137c052a11c8303f1e966aa23b72b65b", size = 27093, upload-time = "2024-12-28T20:16:52.994Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/80/ba/f00296c5c4536967c7d1136107fdb91c48404fe769a4a6fd5ab045629af8/backports_datetime_fromisoformat-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8273fe7932db65d952a43e238318966eab9e49e8dd546550a41df12175cc2be4", size = 52836, upload-time = "2024-12-28T20:16:55.283Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/92/bb1da57a069ddd601aee352a87262c7ae93467e66721d5762f59df5021a6/backports_datetime_fromisoformat-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39d57ea50aa5a524bb239688adc1d1d824c31b6094ebd39aa164d6cadb85de22", size = 52798, upload-time = "2024-12-28T20:16:56.64Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/df/ef/b6cfd355982e817ccdb8d8d109f720cab6e06f900784b034b30efa8fa832/backports_datetime_fromisoformat-2.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ac6272f87693e78209dc72e84cf9ab58052027733cd0721c55356d3c881791cf", size = 52891, upload-time = "2024-12-28T20:16:58.887Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/39/b13e3ae8a7c5d88b68a6e9248ffe7066534b0cfe504bf521963e61b6282d/backports_datetime_fromisoformat-2.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:44c497a71f80cd2bcfc26faae8857cf8e79388e3d5fbf79d2354b8c360547d58", size = 52955, upload-time = "2024-12-28T20:17:00.028Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1e/e4/70cffa3ce1eb4f2ff0c0d6f5d56285aacead6bd3879b27a2ba57ab261172/backports_datetime_fromisoformat-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:6335a4c9e8af329cb1ded5ab41a666e1448116161905a94e054f205aa6d263bc", size = 29323, upload-time = "2024-12-28T20:17:01.125Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/f5/5bc92030deadf34c365d908d4533709341fb05d0082db318774fdf1b2bcb/backports_datetime_fromisoformat-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2e4b66e017253cdbe5a1de49e0eecff3f66cd72bcb1229d7db6e6b1832c0443", size = 27626, upload-time = "2024-12-28T20:17:03.448Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/28/45/5885737d51f81dfcd0911dd5c16b510b249d4c4cf6f4a991176e0358a42a/backports_datetime_fromisoformat-2.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:43e2d648e150777e13bbc2549cc960373e37bf65bd8a5d2e0cef40e16e5d8dd0", size = 34588, upload-time = "2024-12-28T20:17:04.459Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/6d/bd74de70953f5dd3e768c8fc774af942af0ce9f211e7c38dd478fa7ea910/backports_datetime_fromisoformat-2.0.3-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:4ce6326fd86d5bae37813c7bf1543bae9e4c215ec6f5afe4c518be2635e2e005", size = 27162, upload-time = "2024-12-28T20:17:06.752Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/ba/1d14b097f13cce45b2b35db9898957578b7fcc984e79af3b35189e0d332f/backports_datetime_fromisoformat-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7c8fac333bf860208fd522a5394369ee3c790d0aa4311f515fcc4b6c5ef8d75", size = 54482, upload-time = "2024-12-28T20:17:08.15Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/e9/a2a7927d053b6fa148b64b5e13ca741ca254c13edca99d8251e9a8a09cfe/backports_datetime_fromisoformat-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4da5ab3aa0cc293dc0662a0c6d1da1a011dc1edcbc3122a288cfed13a0b45", size = 54362, upload-time = "2024-12-28T20:17:10.605Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/99/394fb5e80131a7d58c49b89e78a61733a9994885804a0bb582416dd10c6f/backports_datetime_fromisoformat-2.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58ea11e3bf912bd0a36b0519eae2c5b560b3cb972ea756e66b73fb9be460af01", size = 54162, upload-time = "2024-12-28T20:17:12.301Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/25/1940369de573c752889646d70b3fe8645e77b9e17984e72a554b9b51ffc4/backports_datetime_fromisoformat-2.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8a375c7dbee4734318714a799b6c697223e4bbb57232af37fbfff88fb48a14c6", size = 54118, upload-time = "2024-12-28T20:17:13.609Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b7/46/f275bf6c61683414acaf42b2df7286d68cfef03e98b45c168323d7707778/backports_datetime_fromisoformat-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:ac677b1664c4585c2e014739f6678137c8336815406052349c85898206ec7061", size = 29329, upload-time = "2024-12-28T20:17:16.124Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a2/0f/69bbdde2e1e57c09b5f01788804c50e68b29890aada999f2b1a40519def9/backports_datetime_fromisoformat-2.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ce47ee1ba91e146149cf40565c3d750ea1be94faf660ca733d8601e0848147", size = 27630, upload-time = "2024-12-28T20:17:19.442Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/1d/1c84a50c673c87518b1adfeafcfd149991ed1f7aedc45d6e5eac2f7d19d7/backports_datetime_fromisoformat-2.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:8b7e069910a66b3bba61df35b5f879e5253ff0821a70375b9daf06444d046fa4", size = 34707, upload-time = "2024-12-28T20:17:21.79Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/44/27eae384e7e045cda83f70b551d04b4a0b294f9822d32dea1cbf1592de59/backports_datetime_fromisoformat-2.0.3-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:a3b5d1d04a9e0f7b15aa1e647c750631a873b298cdd1255687bb68779fe8eb35", size = 27280, upload-time = "2024-12-28T20:17:24.503Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a7/7a/a4075187eb6bbb1ff6beb7229db5f66d1070e6968abeb61e056fa51afa5e/backports_datetime_fromisoformat-2.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1b95986430e789c076610aea704db20874f0781b8624f648ca9fb6ef67c6e1", size = 55094, upload-time = "2024-12-28T20:17:25.546Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/03/3fced4230c10af14aacadc195fe58e2ced91d011217b450c2e16a09a98c8/backports_datetime_fromisoformat-2.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffe5f793db59e2f1d45ec35a1cf51404fdd69df9f6952a0c87c3060af4c00e32", size = 55605, upload-time = "2024-12-28T20:17:29.208Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/0a/4b34a838c57bd16d3e5861ab963845e73a1041034651f7459e9935289cfd/backports_datetime_fromisoformat-2.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:620e8e73bd2595dfff1b4d256a12b67fce90ece3de87b38e1dde46b910f46f4d", size = 55353, upload-time = "2024-12-28T20:17:32.433Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d9/68/07d13c6e98e1cad85606a876367ede2de46af859833a1da12c413c201d78/backports_datetime_fromisoformat-2.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4cf9c0a985d68476c1cabd6385c691201dda2337d7453fb4da9679ce9f23f4e7", size = 55298, upload-time = "2024-12-28T20:17:34.919Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/33/45b4d5311f42360f9b900dea53ab2bb20a3d61d7f9b7c37ddfcb3962f86f/backports_datetime_fromisoformat-2.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:d144868a73002e6e2e6fef72333e7b0129cecdd121aa8f1edba7107fd067255d", size = 29375, upload-time = "2024-12-28T20:17:36.018Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/be/03/7eaa9f9bf290395d57fd30d7f1f2f9dff60c06a31c237dc2beb477e8f899/backports_datetime_fromisoformat-2.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90e202e72a3d5aae673fcc8c9a4267d56b2f532beeb9173361293625fe4d2039", size = 28980, upload-time = "2024-12-28T20:18:06.554Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/80/a0ecf33446c7349e79f54cc532933780341d20cff0ee12b5bfdcaa47067e/backports_datetime_fromisoformat-2.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2df98ef1b76f5a58bb493dda552259ba60c3a37557d848e039524203951c9f06", size = 28449, upload-time = "2024-12-28T20:18:07.77Z" },
]
[[package]]
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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "backrefs"
version = "7.0"
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" },
]
[[package]]
name = "bandit"
version = "1.9.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "pyyaml" },
{ name = "rich" },
{ name = "stevedore" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" },
]
[[package]]
name = "bandit-sarif-formatter"
version = "1.1.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jschema-to-python" },
{ name = "sarif-om" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a1/2d/fa7f7769e487ec03a28a62b780f1c0a2e788359ef1a18b8a62744d42ee43/bandit_sarif_formatter-1.1.1.tar.gz", hash = "sha256:2b6299617c559e41cccf4c9cdf0724dbfc9dc276ecbd94c28074db0d3363ef72", size = 7340, upload-time = "2019-10-05T20:24:04.638Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a1/2d/fa7f7769e487ec03a28a62b780f1c0a2e788359ef1a18b8a62744d42ee43/bandit_sarif_formatter-1.1.1.tar.gz", hash = "sha256:2b6299617c559e41cccf4c9cdf0724dbfc9dc276ecbd94c28074db0d3363ef72", size = 7340, upload-time = "2019-10-05T20:24:04.638Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d5/9d/4e633743766b159d20cca63fc56c0817633d0f99c49607d72b796a386d46/bandit_sarif_formatter-1.1.1-py3-none-any.whl", hash = "sha256:2a8351cecc03b265ec7546794b9c54a988d0b7282db544de913dc6c5a46a50fb", size = 8466, upload-time = "2019-10-05T20:24:03.023Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/9d/4e633743766b159d20cca63fc56c0817633d0f99c49607d72b796a386d46/bandit_sarif_formatter-1.1.1-py3-none-any.whl", hash = "sha256:2a8351cecc03b265ec7546794b9c54a988d0b7282db544de913dc6c5a46a50fb", size = 8466, upload-time = "2019-10-05T20:24:03.023Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.34"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/01/ac/178eb7f96bb6d5771105fe998b8b34512ef3f7ce9e2f1ab8d018df935bee/boto3-1.43.34.tar.gz", hash = "sha256:444207c6c883d4df3ea3b2c36df43ad492b86e0b889eebd2fc1d5ea8db0a8a1a", size = 112656, upload-time = "2026-06-19T19:33:39.366Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/01/ac/178eb7f96bb6d5771105fe998b8b34512ef3f7ce9e2f1ab8d018df935bee/boto3-1.43.34.tar.gz", hash = "sha256:444207c6c883d4df3ea3b2c36df43ad492b86e0b889eebd2fc1d5ea8db0a8a1a", size = 112656, upload-time = "2026-06-19T19:33:39.366Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4c/5f/ac0872df61c3cea3539252f867cf8d76c226e4952f52a981b3fa54381060/boto3-1.43.34-py3-none-any.whl", hash = "sha256:42595057324606928c6e2432b3093978e4d722e0d432bce942f2a385702c0a43", size = 140029, upload-time = "2026-06-19T19:33:37.807Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4c/5f/ac0872df61c3cea3539252f867cf8d76c226e4952f52a981b3fa54381060/boto3-1.43.34-py3-none-any.whl", hash = "sha256:42595057324606928c6e2432b3093978e4d722e0d432bce942f2a385702c0a43", size = 140029, upload-time = "2026-06-19T19:33:37.807Z" },
]
[[package]]
name = "botocore"
version = "1.43.34"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3c/0d/559cdceb9f6acea6b91404970b7973e28a4434fa8a70eb1416b0af478d86/botocore-1.43.34.tar.gz", hash = "sha256:ccc973cf30c6445b30afe5760f6dc949a80f1f862cb23d9c45747f2c814ece77", size = 15591382, upload-time = "2026-06-19T19:33:28.561Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/0d/559cdceb9f6acea6b91404970b7973e28a4434fa8a70eb1416b0af478d86/botocore-1.43.34.tar.gz", hash = "sha256:ccc973cf30c6445b30afe5760f6dc949a80f1f862cb23d9c45747f2c814ece77", size = 15591382, upload-time = "2026-06-19T19:33:28.561Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d5/ea/dc5aab38e2b3f63380810465fab92c836e9e8bce458eba4a8a896f25e1d2/botocore-1.43.34-py3-none-any.whl", hash = "sha256:238a0269f33c5914b9343900b44767e783b3e8b6dcb6e065eac8b4495601c5df", size = 15277590, upload-time = "2026-06-19T19:33:24.562Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/ea/dc5aab38e2b3f63380810465fab92c836e9e8bce458eba4a8a896f25e1d2/botocore-1.43.34-py3-none-any.whl", hash = "sha256:238a0269f33c5914b9343900b44767e783b3e8b6dcb6e065eac8b4495601c5df", size = 15277590, upload-time = "2026-06-19T19:33:24.562Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "build"
version = "1.5.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama", marker = "os_name == 'nt'" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10.2'" },
@@ -300,31 +300,31 @@ dependencies = [
{ name = "pyproject-hooks" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" },
]
[[package]]
name = "bump2version"
version = "1.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/29/2a/688aca6eeebfe8941235be53f4da780c6edee05dbbea5d7abaa3aab6fad2/bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6", size = 36236, upload-time = "2020-10-07T18:38:40.119Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/29/2a/688aca6eeebfe8941235be53f4da780c6edee05dbbea5d7abaa3aab6fad2/bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6", size = 36236, upload-time = "2020-10-07T18:38:40.119Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1d/e3/fa60c47d7c344533142eb3af0b73234ef8ea3fb2da742ab976b947e717df/bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", size = 22030, upload-time = "2020-10-07T18:38:38.148Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/e3/fa60c47d7c344533142eb3af0b73234ef8ea3fb2da742ab976b947e717df/bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", size = 22030, upload-time = "2020-10-07T18:38:38.148Z" },
]
[[package]]
name = "cachecontrol"
version = "0.14.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]
@@ -335,365 +335,365 @@ filecache = [
[[package]]
name = "certifi"
version = "2026.6.17"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
]
[[package]]
name = "cffi"
version = "2.0.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" },
- { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" },
- { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" },
- { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" },
- { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" },
- { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" },
- { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" },
- { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" },
- { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" },
- { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" },
- { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" },
- { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" },
- { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
- { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
- { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
- { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
- { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
- { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
- { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
- { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
- { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
- { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
- { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
- { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
- { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
- { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
- { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
- { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
- { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
- { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
- { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
- { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
- { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
- { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
- { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
- { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
- { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
- { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
- { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
- { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
- { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
- { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
- { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
- { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
- { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
- { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
- { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
- { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
- { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
- { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
- { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
- { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
- { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
- { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
- { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
- { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
- { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
- { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
- { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
- { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
- { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
- { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
- { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
- { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
- { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
- { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
- { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
- { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
- { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
- { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
- { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "cfgv"
version = "3.5.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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "cfn-lint"
version = "1.51.5"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "aws-sam-translator" },
{ name = "jsonpatch" },
- { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "networkx", version = "3.4.2", source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }, marker = "python_full_version < '3.11'" },
+ { name = "networkx", version = "3.6.1", source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }, marker = "python_full_version >= '3.11'" },
{ name = "pyyaml" },
{ name = "regex" },
{ name = "sympy" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/05/69/d9e8f555ded51061f73aa2cfbe30b0b6d5273724f5563655f6dc8b77ecdd/cfn_lint-1.51.5.tar.gz", hash = "sha256:018a00f1f9eeadc196afbdc0ac8c6221c29411747c8dcff2f431d48d4080c83b", size = 4114038, upload-time = "2026-06-15T21:58:13.885Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/69/d9e8f555ded51061f73aa2cfbe30b0b6d5273724f5563655f6dc8b77ecdd/cfn_lint-1.51.5.tar.gz", hash = "sha256:018a00f1f9eeadc196afbdc0ac8c6221c29411747c8dcff2f431d48d4080c83b", size = 4114038, upload-time = "2026-06-15T21:58:13.885Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c9/18/f881634f7a03ca3914f8943ed4aa46a5f64fb121c99b983c663760931674/cfn_lint-1.51.5-py3-none-any.whl", hash = "sha256:e17c11a1485a2586c5ddf48d0041f8fa3da3038612fd7dbc559ec87935f53452", size = 6150401, upload-time = "2026-06-15T21:58:11.573Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/18/f881634f7a03ca3914f8943ed4aa46a5f64fb121c99b983c663760931674/cfn_lint-1.51.5-py3-none-any.whl", hash = "sha256:e17c11a1485a2586c5ddf48d0041f8fa3da3038612fd7dbc559ec87935f53452", size = 6150401, upload-time = "2026-06-15T21:58:11.573Z" },
]
[[package]]
name = "chardet"
version = "5.2.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.7"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" },
- { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" },
- { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" },
- { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" },
- { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" },
- { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" },
- { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" },
- { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" },
- { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" },
- { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" },
- { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" },
- { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" },
- { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" },
- { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" },
- { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" },
- { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" },
- { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" },
- { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" },
- { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" },
- { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" },
- { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" },
- { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" },
- { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" },
- { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" },
- { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" },
- { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" },
- { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" },
- { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" },
- { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" },
- { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" },
- { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" },
- { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" },
- { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
- { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
- { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
- { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
- { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
- { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
- { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
- { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
- { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
- { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
- { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
- { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
- { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
- { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
- { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
- { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
- { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
- { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
- { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
- { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
- { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
- { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
- { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
- { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
- { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
- { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
- { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
- { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
- { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
- { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
- { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
- { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
- { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
- { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
- { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
- { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
- { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
- { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
- { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
- { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
- { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
- { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
- { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
- { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
- { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
- { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
- { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
- { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
- { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
- { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
- { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
- { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
- { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
- { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
- { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
- { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
- { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
- { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
- { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
- { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
- { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
- { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
- { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
- { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
- { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "click"
version = "8.1.8"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
-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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "colorama"
version = "0.4.6"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "coverage"
version = "7.14.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/9c/a3/3834a5564fe8f32154cd7032400d3c2f9c565b2a373fa671f2bbdad6f634/coverage-7.14.2.tar.gz", hash = "sha256:7a2da3d81cfe17c18038c6d98e6592aa9147d596d056119b0ee612c3c8bd5230", size = 923982, upload-time = "2026-06-20T14:49:30.885Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/56/7f/551ebe25fa3de95ebbd3528b01ffd672b418e9c521b8555f85fb8aca21f8/coverage-7.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b75818e3046e9319143157f3dc4b43679a550c2060a17cbf3e39cc0b552925", size = 220230, upload-time = "2026-06-20T14:47:09.177Z" },
- { url = "https://files.pythonhosted.org/packages/6a/ec/a444a1a21b46e54298357977d8ab6c388e5755bc79effaf587808fdb405f/coverage-7.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66b08ba4c5cbf0eaa2e9692b203073f198d5d469d8b15d1c7a4854ce7032b2e2", size = 220750, upload-time = "2026-06-20T14:47:11.243Z" },
- { url = "https://files.pythonhosted.org/packages/38/e5/0dce79f914e31fa0810ab770b06cc638fe5137af259649fafd4daeb2c8e5/coverage-7.14.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:70f266b536c590060b707dddfb6cf9f17e24fd30b992242e774543d256265c43", size = 247487, upload-time = "2026-06-20T14:47:12.564Z" },
- { url = "https://files.pythonhosted.org/packages/52/bb/aaca2c75ca6a5da71c3f413ac5920fe9f6e1aad387dae52a3315adf313e0/coverage-7.14.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb40cac5b1a6378fdccc99268f1033112ee4636e4fd9aaf240f6930d1fcea12c", size = 249316, upload-time = "2026-06-20T14:47:13.938Z" },
- { url = "https://files.pythonhosted.org/packages/37/b3/b45f5edd19ab9f79f7c6a4da2b4d1bd5969e0d7605fe197b60405c7129da/coverage-7.14.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c301fe9990cb5c081bf4881cb498743807c8e0e93fad7b85c02788456492ef8", size = 251182, upload-time = "2026-06-20T14:47:15.144Z" },
- { url = "https://files.pythonhosted.org/packages/f6/7c/ad5fd04da4565e7c9ad35080a5fb4762ed2d0f4893ac7eff6a1e7364e79f/coverage-7.14.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d67b0462c8a3c3d93033e7c79cacdfc57d08e5220d9115bcb24a23edf5a5900d", size = 253095, upload-time = "2026-06-20T14:47:16.468Z" },
- { url = "https://files.pythonhosted.org/packages/00/f9/41279b303e8773e1fd9a621a80159c7ed7b643dd9c7e85fd7fc3c88ebdaa/coverage-7.14.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e763087828ee9644f0c89c57f9b75f0a50fdf3e8f5d8fac5cfc351337e89a99", size = 248203, upload-time = "2026-06-20T14:47:17.758Z" },
- { url = "https://files.pythonhosted.org/packages/6c/ad/205dddd96954fcf7c7f0b509af614c0edc2a547115ad2fb52a8fc9cbaa41/coverage-7.14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6d4da2baab6d96ceedd9176b3c142e1198b0310bc8dc04e18a3caab65c3a322c", size = 249223, upload-time = "2026-06-20T14:47:19.276Z" },
- { url = "https://files.pythonhosted.org/packages/3f/da/46c437176338ece41effbbd07d2941378db04b3e618dce68197d59a870b4/coverage-7.14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ab565a405bfdea61260145d8cc987aa66d1998fd0e0ccd4348008f4e6a39ee33", size = 247226, upload-time = "2026-06-20T14:47:20.613Z" },
- { url = "https://files.pythonhosted.org/packages/0c/a7/42dfcb471dedb51d366762528e53f232427e8d897b92a9106b4aacec74fc/coverage-7.14.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c13230b688fbb9122251b74daa092175811eb64cb7bd1c98e2c8193dfa2b0bd5", size = 251039, upload-time = "2026-06-20T14:47:22.027Z" },
- { url = "https://files.pythonhosted.org/packages/3c/29/987df1a9f8843d3b1f50cb47cca0b10c983e84a3b1b9f6965796f07efa4e/coverage-7.14.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:014c83ba1ec97993cfe94e77fe6b56daa76bc0c218b86938971574c28942d044", size = 247497, upload-time = "2026-06-20T14:47:23.374Z" },
- { url = "https://files.pythonhosted.org/packages/8f/1c/919b6624c35161d183c43f57fca52ebcc5a59a7b3fa52fe0d0c3067469f5/coverage-7.14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6caf54ffbf84b30470a8118f275afee9234e616572e4e41bae1dc19198c37294", size = 248100, upload-time = "2026-06-20T14:47:24.621Z" },
- { url = "https://files.pythonhosted.org/packages/6f/15/acbc7b5a6184f92d4875b820477f0bbb3a87371408f5ef73a575bdd3b8e1/coverage-7.14.2-cp310-cp310-win32.whl", hash = "sha256:4bf9d8a35f77df5638c61b5012ba5225109ec1cc15bc5eb097036b3c3cc939f3", size = 222282, upload-time = "2026-06-20T14:47:25.893Z" },
- { url = "https://files.pythonhosted.org/packages/2f/3f/944e24fdb2e88549b58bfd5a51a3a66481cf21154c7aa1a494597c870125/coverage-7.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:c1f17a8caebe0facd4556b1e0adfe0987c17feebed88e7bb6b5365c45c84c5d6", size = 222910, upload-time = "2026-06-20T14:47:27.331Z" },
- { url = "https://files.pythonhosted.org/packages/04/d5/d0e511247f84fa88ae7da68403cbd3bf9d2a5fc48f5d6618a6846b275632/coverage-7.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:909f265c8c41f04c824bf741b2601fdcb56cab4bf56e018996b6494192ba0f58", size = 220352, upload-time = "2026-06-20T14:47:28.61Z" },
- { url = "https://files.pythonhosted.org/packages/03/4a/ecaff6db72e6c1782ca51336e391393f1e9cc6e4412d6c3da8b7d5075adf/coverage-7.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8102deaf911938233f760426e6a5e287388521de95111d5c8de26c8a1028924", size = 220855, upload-time = "2026-06-20T14:47:29.972Z" },
- { url = "https://files.pythonhosted.org/packages/34/9a/cf950cd8e8df06ee5941276e69f81647005360421be523d5ca18f658e143/coverage-7.14.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:851f49e7bd7d1cdaf328f3133942b252d5e3d3380690131f423cba8e435b87f5", size = 251276, upload-time = "2026-06-20T14:47:31.413Z" },
- { url = "https://files.pythonhosted.org/packages/9d/08/f973be32c9a095e4bb2d3a7bdcb2f9c117e39d4062471ffffae3623f6c51/coverage-7.14.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04cb445bed86aaf00aaa97d41a8b6e30f100f21e81c34caaec4efc684cb57768", size = 253189, upload-time = "2026-06-20T14:47:32.727Z" },
- { url = "https://files.pythonhosted.org/packages/96/aa/f3a50952ba553d442d94b793e5dede25d426b02e5e011e9a9dd225c002d3/coverage-7.14.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7471bc920d97c51c37ea8127f13b2adca43c3d78c53313b26a1f428e99d2c254", size = 255299, upload-time = "2026-06-20T14:47:34.019Z" },
- { url = "https://files.pythonhosted.org/packages/e0/29/9a4c491986f4d637ed64961ae56721661fc21b6b767d280848d0c708756a/coverage-7.14.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:da5057e1bb257c967feee8ba67f3ebf379e801c7717f238b3d8c9caf00fc8f93", size = 257255, upload-time = "2026-06-20T14:47:35.397Z" },
- { url = "https://files.pythonhosted.org/packages/dd/61/d2a5b48007f6a212f321c36cf5486feb80505d2d00dfb1163aad2da71197/coverage-7.14.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33c0da852e8a40246cd8e20cf3b2fc17ca52a45e9b5f7983c93db26f5d24b87b", size = 251417, upload-time = "2026-06-20T14:47:36.677Z" },
- { url = "https://files.pythonhosted.org/packages/ea/25/8df66ae25b401d4529e1d0617af20d9695d171ea4ffec4ca9dffc5dc37b7/coverage-7.14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f48a85bb437fab7782021c40bfee6b15146928b96960d008ace41b6901a0f21d", size = 252991, upload-time = "2026-06-20T14:47:38.027Z" },
- { url = "https://files.pythonhosted.org/packages/e3/7b/16bdc9116dd8bf412a421a7227daa65ad9f12bef0685b13c1bd1c12e6d4c/coverage-7.14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f44e7579a769a21d5b5e3166916bfe30ee175aaffff750324cbb11be2dbec5ad", size = 251051, upload-time = "2026-06-20T14:47:39.26Z" },
- { url = "https://files.pythonhosted.org/packages/0a/f8/b7dbed84274dcc69ddb9c0fe72ec1260830473e0d6c299dcf087a0567f7c/coverage-7.14.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:78853ca3c6ca2f012daa2b07dbabbb8db0f09d4dbe8ee828d294b3445d3f4cd8", size = 254817, upload-time = "2026-06-20T14:47:40.995Z" },
- { url = "https://files.pythonhosted.org/packages/c6/07/4659e6bed01a25a0effb4952e8e75fd157038fe5f2829b0f69c6811c2033/coverage-7.14.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c9c2795ee3692097ff226ab806005d36bb9691fca9b35353542b57ea749cc830", size = 250772, upload-time = "2026-06-20T14:47:42.306Z" },
- { url = "https://files.pythonhosted.org/packages/26/f4/45019da4cd6cd1df3042476447449d62a76a201f6b3556aa40ac31bce20b/coverage-7.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2f5cc48a845d755b6db236f8c29c2b54773eb4c7e4ee2ead43812d73718784b0", size = 251679, upload-time = "2026-06-20T14:47:43.703Z" },
- { url = "https://files.pythonhosted.org/packages/92/e5/76d75fa2ffe0285d3f2608d1bb241fc245cf98fe614d52118427dd6ccdaa/coverage-7.14.2-cp311-cp311-win32.whl", hash = "sha256:9c61cb7eaabcfa609c5bc0f5ff5869d72a2f02f17994e5fba5f971de516f3c82", size = 222445, upload-time = "2026-06-20T14:47:45.137Z" },
- { url = "https://files.pythonhosted.org/packages/57/59/696c64547e5c8b9ed31532e9c7a5f9b6474054da93f8ab07f8baf7365c57/coverage-7.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:e715909b0966d1774d8a26e14e2f4a3ae75909dca526901c6306286b2dcbfbdc", size = 222922, upload-time = "2026-06-20T14:47:46.67Z" },
- { url = "https://files.pythonhosted.org/packages/63/72/646a28100462996c11b98e27d6786cd61f48100d1479804846a3e1e5bf9b/coverage-7.14.2-cp311-cp311-win_arm64.whl", hash = "sha256:9193f7150937a4fd836b10eaa123e15d98e961d1fabac07e60adf2d4785f888a", size = 222468, upload-time = "2026-06-20T14:47:48.119Z" },
- { url = "https://files.pythonhosted.org/packages/d0/d9/bdd141aa2c605096a8ef63b8435fd4f5fec78946a3cb7b9145840ec78291/coverage-7.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37c94712e533ea06f0b1e4d934811c520b1914ce0e4da3916220717aa7a86bc6", size = 220528, upload-time = "2026-06-20T14:47:49.652Z" },
- { url = "https://files.pythonhosted.org/packages/02/97/d24ae7d2afc62c54a36313d4dedb655c9afbba3003f0f7f1ae81e97af31f/coverage-7.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c050bbc7bba94c77e4ed7438f4fda1babe98ab145691d80aa6f60df934a1468b", size = 220883, upload-time = "2026-06-20T14:47:51.036Z" },
- { url = "https://files.pythonhosted.org/packages/f8/0e/d8f00efd3df0d63e6843ebcbade9e4119d60f5376753c9705d84b014c775/coverage-7.14.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a7af571767a2ee342a171c16fc1b1a07a0bf511606d381703fb7cf397fe49d46", size = 252395, upload-time = "2026-06-20T14:47:52.627Z" },
- { url = "https://files.pythonhosted.org/packages/1c/1c/ab9510dfe1a16a35a10f90efad0d9a9cf61b9876973752968f2ba882f73f/coverage-7.14.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8b4910cce599cd2438f8da65f5ef199a70a1cdb6ab314926df78271ca5954240", size = 255131, upload-time = "2026-06-20T14:47:54.235Z" },
- { url = "https://files.pythonhosted.org/packages/ba/dd/70171e9371003b33dc6b20f527ac216ff91bbe5c1088e754eb8950d79193/coverage-7.14.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c33e9e4878972f430b0cc06de3bf2a28d054a9efb4f8426d27de0d9cb81396ff", size = 256246, upload-time = "2026-06-20T14:47:55.61Z" },
- { url = "https://files.pythonhosted.org/packages/0f/80/a68b1dd81d5c011e17fd6ab0d707d33297df1d0c618114b9b750a2219c80/coverage-7.14.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7967ea55c6dea6becba4d5870e2fa0aa4915a8be7ebff1bb79e6207aa75ce8d", size = 258504, upload-time = "2026-06-20T14:47:56.979Z" },
- { url = "https://files.pythonhosted.org/packages/8e/7b/40baaa946189f5317cd77d484e39b9b0727d02ebada0a12162374f2faee2/coverage-7.14.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1322f237c2979b84096f4239c17828ff17fea6b3bbe96c44381c5f587c44c26", size = 252808, upload-time = "2026-06-20T14:47:58.418Z" },
- { url = "https://files.pythonhosted.org/packages/d5/05/b19517b09c43d1e8591de6c13178b0c03166c31e1adbebda378e64c66b9a/coverage-7.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77849525340c99f516d793dddbcee16b18d50af892ac43c8de1a6f343d41e3b5", size = 254166, upload-time = "2026-06-20T14:48:00.004Z" },
- { url = "https://files.pythonhosted.org/packages/ae/f5/6e65da5957e041d2094a9b97736628dd80160f1cc007a50790bbb2668c1a/coverage-7.14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef11695493ec3f06f7b2678ca274bcabb4ca04057317df268ddbfd8b05f661a8", size = 252310, upload-time = "2026-06-20T14:48:01.458Z" },
- { url = "https://files.pythonhosted.org/packages/2d/de/01b5274f0db63175b04d9354eff68d2d268b8b57a1b2db7d3dcb1f2c9dbb/coverage-7.14.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8134f0e0723e080d1c27bbe8fc149f0162e429fa1852482150015d0fce83eaf1", size = 256379, upload-time = "2026-06-20T14:48:02.981Z" },
- { url = "https://files.pythonhosted.org/packages/71/d6/9a2ffbca41e2f8f86f61e8b78b86afa433ec8cdeac4908ace93a28fe3ff0/coverage-7.14.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:914eead2b843fc357f733b3fe39cc94f1b53d466e8cfe03080b1ed9d24ccfc73", size = 251880, upload-time = "2026-06-20T14:48:04.463Z" },
- { url = "https://files.pythonhosted.org/packages/e3/ff/20bd54a43c88c08f474e6cb355a97e024e38412873ef0a581629abe1e26f/coverage-7.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4b2d5e847fb7958583b74910cc19e5ec4ece514487385677b26433b2546116e", size = 253753, upload-time = "2026-06-20T14:48:05.99Z" },
- { url = "https://files.pythonhosted.org/packages/35/2a/2b3482c30d8344f301d8df6ff232a321f2ab87d5ac97ba21891a68638131/coverage-7.14.2-cp312-cp312-win32.whl", hash = "sha256:e753db9e40dda7302e0ac3e1e6e1325fb7f7b4694f87a7314ab15dd5d57911a7", size = 222584, upload-time = "2026-06-20T14:48:07.361Z" },
- { url = "https://files.pythonhosted.org/packages/f6/5e/83934ffff147edd313fe925db426e8f7ccad9e4663262eb5c4db4e345658/coverage-7.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:d32e5ca5f16dafb269ee50b60d32b00c704b3f6f78e238105f1d94a3a5f24bf5", size = 223118, upload-time = "2026-06-20T14:48:08.837Z" },
- { url = "https://files.pythonhosted.org/packages/bb/ee/616b4f38a34f076f3045d3eedfa764d34d82e6a6cc6b300acb0f1ff22a98/coverage-7.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:dc366f158e2fb2add9d4e57338ca48f12611024278688ee657eb0b853fcb5de5", size = 222504, upload-time = "2026-06-20T14:48:10.436Z" },
- { url = "https://files.pythonhosted.org/packages/6d/09/b5b334c27960e7aac0003b96491bada7838dc641099fa64a1a598abf33cd/coverage-7.14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e5f077641a6713ce9d38df9e85d4fb9e008677fc0775cbaeb32ddfc3b319d4ca", size = 220552, upload-time = "2026-06-20T14:48:11.847Z" },
- { url = "https://files.pythonhosted.org/packages/79/20/879a000c319b4df7b50e4d688c0f7c0f6b5ac9d7b18848cbc00eabf26efe/coverage-7.14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0907f39b49ae818fe8af50aaa0f19afbc8ca164aea0865181ca7af17a3ac690b", size = 220919, upload-time = "2026-06-20T14:48:13.397Z" },
- { url = "https://files.pythonhosted.org/packages/f6/b7/326dded4371bab60f42215797944a356e4d81a3cee106121c7f7dd531604/coverage-7.14.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734d47669118d75c28981e562d4530ceb77342d31ffef6def5edd5ad4f05d7b", size = 251917, upload-time = "2026-06-20T14:48:14.931Z" },
- { url = "https://files.pythonhosted.org/packages/eb/14/b3232ba218a0d1a70883d2675f18ff465de9e8e5e3346e81dc2b079838bd/coverage-7.14.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d9a1b5813d00ea6151f6ccf64d1fa16892771dfdda12ba87162d15ec4ea3e1e", size = 254515, upload-time = "2026-06-20T14:48:16.545Z" },
- { url = "https://files.pythonhosted.org/packages/b7/7a/d77bcbee1cad71b42776574114b462225cc9125b4982f43da1b66adc850f/coverage-7.14.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f0a80f4c8ac3f774210b1cc1bc0e31e75502f2818dda9a144ff90e702c4d91d", size = 255749, upload-time = "2026-06-20T14:48:18.214Z" },
- { url = "https://files.pythonhosted.org/packages/86/86/97377937b29e9e44a1529bb20cb74dbcf80ed9006d87d7e742ff69e44b67/coverage-7.14.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e66f3f22d6c1515ce70f2e7c3e9c6f3ff0ff33480125c9f9c53e8f6508e30f", size = 257882, upload-time = "2026-06-20T14:48:19.7Z" },
- { url = "https://files.pythonhosted.org/packages/c1/a4/0fc8fe68bc505450bb068a2823ac7797bd8495240ccb8b4a5a1da1ee7e62/coverage-7.14.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a2c37c3114f87ca7f10113756026eecb49656514debad600dcbec21f355ccea", size = 252144, upload-time = "2026-06-20T14:48:21.176Z" },
- { url = "https://files.pythonhosted.org/packages/8d/4a/450094ddc41ab0d2eb4a0457b3856400ea3329568d1303696e85de099ae6/coverage-7.14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b16a7959d04b1497281c062c180413565c3f3469211d78799ad5b9a75f67796", size = 253882, upload-time = "2026-06-20T14:48:22.701Z" },
- { url = "https://files.pythonhosted.org/packages/d0/28/2f6ae6d98265d9aa6bac311c4a93403675905b03aca95dc4373080279d75/coverage-7.14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6466c6999545cf00c4c142dfcbbf2db396dc735f005dcf8f91d57e351a79472b", size = 251846, upload-time = "2026-06-20T14:48:24.295Z" },
- { url = "https://files.pythonhosted.org/packages/c2/6e/707281468400794d52874e8fb5e38ff7578a0ff32ed49fe4fe85f192d0fc/coverage-7.14.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c60915ebb8f562317ba5ff6b8c32e25c0882289b201a9f2fb2987f91efd95d8", size = 256002, upload-time = "2026-06-20T14:48:26.015Z" },
- { url = "https://files.pythonhosted.org/packages/c2/83/5e963120de4011257a950ce4cfb7fc833ddf3fee19db495268d3dec28154/coverage-7.14.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:33b830850488acbcd358c78a4fecfafe7031667b4da8ddff5546295dc962cdeb", size = 251665, upload-time = "2026-06-20T14:48:27.654Z" },
- { url = "https://files.pythonhosted.org/packages/e9/78/66b482cd525083bcc0bc894c16db79dabac37490065b53b07d6e8ab77202/coverage-7.14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d0f845539230b8269aec902bc978b0cc403f52f002d18a04492efc943404d0bc", size = 253435, upload-time = "2026-06-20T14:48:29.354Z" },
- { url = "https://files.pythonhosted.org/packages/e6/61/0663fb8cb530c8b11819b920109694eee95a3b22960a9495be0200f657f1/coverage-7.14.2-cp313-cp313-win32.whl", hash = "sha256:a8ac51a2e441e9119b9395f4d893fbc4934c64c8ba58be9b9eaa85591249e548", size = 222591, upload-time = "2026-06-20T14:48:31.142Z" },
- { url = "https://files.pythonhosted.org/packages/a6/47/1536d2b009c2848c3682500f497053f4645e70911afe02f594000997831a/coverage-7.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:039b264cdb31c44b48f9821e2afbf8f37df49e0fb837e24a942918b36c567e31", size = 223134, upload-time = "2026-06-20T14:48:32.696Z" },
- { url = "https://files.pythonhosted.org/packages/28/9a/33ba4f335dd60bb34350318283d784f46018070e67b7d4df7c910ec9d9a0/coverage-7.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:7f2ef591e381cc36b8e53334e1b842c760c520c8a52d01e8626209400e93fe6a", size = 222529, upload-time = "2026-06-20T14:48:34.237Z" },
- { url = "https://files.pythonhosted.org/packages/fc/bc/120390669817ede714ab141ae0a2a73240fd7354aac992c41dc0bd19570f/coverage-7.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7a0d1f026b72d627fa5c8a57cbc86ad209b64aa2a65833c83b290ace5cbee126", size = 220593, upload-time = "2026-06-20T14:48:35.755Z" },
- { url = "https://files.pythonhosted.org/packages/4f/a3/7f1cfacd76af91e585f7ad689d7168002b444ed2a8ce59f2daaff10089b5/coverage-7.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4d2b86f81c1c9310a7e774e3cc9e927a3d0bf583ecbfa01498dd626930025428", size = 220925, upload-time = "2026-06-20T14:48:37.35Z" },
- { url = "https://files.pythonhosted.org/packages/e7/10/6514b2525bb672eb8b43703e46d061d694111db21efe7609db722df2233f/coverage-7.14.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d76bdc1f9396ae70a55d050cf9743d88141c62ce0a22a3f627fab1d11c2f8bc6", size = 251974, upload-time = "2026-06-20T14:48:39.109Z" },
- { url = "https://files.pythonhosted.org/packages/23/b4/4533091541c6620ecd68115bbfa1c61265b775618adef3a5fd137f4582e9/coverage-7.14.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cda36d8e7bfd63b3e44e75163265429caa5d935b672b00f71bccc8c010518c64", size = 254479, upload-time = "2026-06-20T14:48:40.871Z" },
- { url = "https://files.pythonhosted.org/packages/06/af/e251a143d5d106385dbca696c553afab6b69f7f6bc376a34e089cc0b8b32/coverage-7.14.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0904f3b79d7b845bef0715afe1900da634d12b97f05b9479cb472880ca07cb9c", size = 255824, upload-time = "2026-06-20T14:48:42.608Z" },
- { url = "https://files.pythonhosted.org/packages/9c/53/9e5876e60efbaa79d743d1948a5015ddc05b808db1cd62228acf83e87d43/coverage-7.14.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6795ca4198d6cb7fc2c6163214f6555a6bc5f0ae1e268e76139dec4b37c4499", size = 258139, upload-time = "2026-06-20T14:48:44.263Z" },
- { url = "https://files.pythonhosted.org/packages/85/5a/d35a4f431fb594e46b81cad4a13b470b017e918f347c1c0b260f7494fa1e/coverage-7.14.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c41e9b60fc0fa57f5d73306417d2f9d668202cca6944f9435878c55a5e7ae213", size = 252002, upload-time = "2026-06-20T14:48:45.961Z" },
- { url = "https://files.pythonhosted.org/packages/0c/e2/f5b304c8139c606c4f1b230d3a257d0c88edfbbdf06c58364f07625dc45c/coverage-7.14.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419d2aadd5746efc2e9df0f33c05570d8192e6f6a6098ab05acce586f44ce8a5", size = 253832, upload-time = "2026-06-20T14:48:47.582Z" },
- { url = "https://files.pythonhosted.org/packages/86/bc/bbbd283daa6be4f68aad4ad4066fd39ae98e4174db8c03ab26c5803d6234/coverage-7.14.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1c5d273c5f1411c0d26c4f066c398d4a434b1f97bb5fa409189bedce86d4add4", size = 251799, upload-time = "2026-06-20T14:48:49.42Z" },
- { url = "https://files.pythonhosted.org/packages/69/8d/0745fceb89c9e5f7dd8ed243d97dc8561b7a95545741e2409d2b34654824/coverage-7.14.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5fe465bc691264adce601527a972990c1174075d86bcbe9968fd20c95e0b1948", size = 256075, upload-time = "2026-06-20T14:48:51.065Z" },
- { url = "https://files.pythonhosted.org/packages/a2/a0/441d9a5255cf021ab41ee00c014a4607d1c72d5e5bef0a4fdaa5be86a907/coverage-7.14.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6fbb61617af1c56f95d53170ae9fa6c9aef6de1abd02fcc50064bfc672efb18d", size = 251612, upload-time = "2026-06-20T14:48:52.653Z" },
- { url = "https://files.pythonhosted.org/packages/50/37/3d19c5e32d4a529c068eb296abfa3e455bd2c0f9311ecf26280f408ff8e0/coverage-7.14.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e1eff22b831dfd5694989cc1f0789980f18391f614ac67c851af9a8e6d25e9ba", size = 253270, upload-time = "2026-06-20T14:48:54.3Z" },
- { url = "https://files.pythonhosted.org/packages/3d/b0/54dd13937297518da6d092cc2c39d9340ec2194bdfa92e0a64694d643e23/coverage-7.14.2-cp314-cp314-win32.whl", hash = "sha256:58e91be0a233adef698d3e6be54f10401bb91fd7854c0d4c4d50e0d3711e72f1", size = 222796, upload-time = "2026-06-20T14:48:56.084Z" },
- { url = "https://files.pythonhosted.org/packages/51/45/7a10e0909919686e335fdd95869cfb222d55243ebff27dc5cf59ca259a1f/coverage-7.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:d8429bf97906bfe6c61f9dbfb3342e0d88120da61939da8bd04f830cc3eab3b8", size = 223285, upload-time = "2026-06-20T14:48:57.729Z" },
- { url = "https://files.pythonhosted.org/packages/2e/03/9cb197eb4b3d1a2eccb2537c226a93c80522c5b8afc5dd93e1993d7bb021/coverage-7.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:13609d9d77249447aa73357b14831b0f3b95f275026c9ff20dd105f981f53a0c", size = 222712, upload-time = "2026-06-20T14:48:59.413Z" },
- { url = "https://files.pythonhosted.org/packages/d6/3c/e59f498511080d20bf866b0af9eeab820feb91547dae2084cb9bb7fb0e58/coverage-7.14.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9818486c2bac88ae931df7e04905ee29bef49fd218c00f5f02bed4855254a101", size = 221325, upload-time = "2026-06-20T14:49:01.447Z" },
- { url = "https://files.pythonhosted.org/packages/d3/37/8d7955f7e701e69198bd0a0132ea76518c078a635b930a4924e2ccfa70f0/coverage-7.14.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:58055adffabfa243516a197aa9f85f0dd56d905b0fba1a10193269759c29ccb0", size = 221594, upload-time = "2026-06-20T14:49:03.13Z" },
- { url = "https://files.pythonhosted.org/packages/34/7a/6738e1e1533ce8ec4e2e472696eefdd4723864d7efaa140e433053bf576a/coverage-7.14.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:535747dbc200349d7fb434cffcb28e770f0290f69b225f56dc3803aa7210cdea", size = 262957, upload-time = "2026-06-20T14:49:04.829Z" },
- { url = "https://files.pythonhosted.org/packages/35/c4/d1be863cd39e0955904315fece67c5c23e046563f5eea0ceac16c547a759/coverage-7.14.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:420c66e35d85c0ca5dc6a38147d83ef239762542900e5921ebbdb89333c540ea", size = 265081, upload-time = "2026-06-20T14:49:07.018Z" },
- { url = "https://files.pythonhosted.org/packages/72/7f/412df3c3c251284a11834287fd6f7e3bb98c528c53e030589e9344a3ef80/coverage-7.14.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2cf17b33773be446a588551ea6a746b2d70dd0bc90dc31f1dd7648975a63c6b", size = 267500, upload-time = "2026-06-20T14:49:08.709Z" },
- { url = "https://files.pythonhosted.org/packages/54/68/7d0764e83459455384d5c04179ce2d2a837bef01b9ba463079c6e8b31361/coverage-7.14.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adb4a5fef041f7179bb264203add873c147d169cf2f8d0adae89ff2e51271bac", size = 268619, upload-time = "2026-06-20T14:49:10.405Z" },
- { url = "https://files.pythonhosted.org/packages/14/68/1292164ac70cbcc86ac3982da31a6fbb42bb4bcebf6e5cf73c99cfcfd50d/coverage-7.14.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c012ec357dec9408a83dad5541172a63c5cfa1421709f2e5811480d31ae1b28", size = 262066, upload-time = "2026-06-20T14:49:12.257Z" },
- { url = "https://files.pythonhosted.org/packages/20/44/fd6fdf3f63b6e00a1a9230022d072ded5189576001685706aa6524187c65/coverage-7.14.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dacd0ecd08fda3cb2f85b60cabea7da326dcb2fc15fbb23a88830a80144cc9f2", size = 264953, upload-time = "2026-06-20T14:49:14.13Z" },
- { url = "https://files.pythonhosted.org/packages/39/29/e803fea3da89eaeb5b6b41b3ccd039fe9f3300a900e3803baac1a998529f/coverage-7.14.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f27e980f2feba5dfe7a32b22b125470de69c0bd113c75e16165de909a777f512", size = 262555, upload-time = "2026-06-20T14:49:15.803Z" },
- { url = "https://files.pythonhosted.org/packages/32/3c/b360e48ac68e3236c04cb83658382e7f5be7efbbec2e1faae3dcca432783/coverage-7.14.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:105c00efb65c863630b2b63cbf7b8267e4da2d44b62284efbb19a03b04c337d4", size = 266289, upload-time = "2026-06-20T14:49:17.962Z" },
- { url = "https://files.pythonhosted.org/packages/59/12/1ed6d9274d599c586e2d1aa9818765dcdae6bb52aa88afa2fcd868398191/coverage-7.14.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:571173fa04c8e8d6235ab32ae67fecca97777e2e1b4a1a30f3022c34e397c1c1", size = 261402, upload-time = "2026-06-20T14:49:19.708Z" },
- { url = "https://files.pythonhosted.org/packages/44/17/eb6cf12a4538cda937aefbeabb15377a8a30b377b484e63d31c9da790966/coverage-7.14.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e532f34d42d1a421fa00ed6b7735d14ac2e340256c1bad26a5e1dc1252b0bed7", size = 263715, upload-time = "2026-06-20T14:49:21.427Z" },
- { url = "https://files.pythonhosted.org/packages/8a/ca/4bafdb9d372ab05d6ed3a63e7f00d3195d169d0afea00f617c026e386c19/coverage-7.14.2-cp314-cp314t-win32.whl", hash = "sha256:243971550fb46c3039257f75e65610002d84304c505f609bbd9779e20a653a0a", size = 223103, upload-time = "2026-06-20T14:49:23.24Z" },
- { url = "https://files.pythonhosted.org/packages/35/cb/0765dbd9011d2e47315f1da31e62c5fe231f04a6ec8da213e64c4505896d/coverage-7.14.2-cp314-cp314t-win_amd64.whl", hash = "sha256:60fb0ca084a92da96474b8b405a7ea76dfecac3c68db54383e7934b6f3871169", size = 223934, upload-time = "2026-06-20T14:49:25.347Z" },
- { url = "https://files.pythonhosted.org/packages/4e/ce/373dde027ecd0ae54511430fe7569f838d3a0376b70333ba9fd20c76b836/coverage-7.14.2-cp314-cp314t-win_arm64.whl", hash = "sha256:36a0a3f42ed7dfdbca2a69a541519ffd5064a5692152fc0018109e74370d7345", size = 223249, upload-time = "2026-06-20T14:49:27.241Z" },
- { url = "https://files.pythonhosted.org/packages/a3/5e/a8ba14ceb014f39bd5e3f7077150718c7de61c01ce326bfe7e8eae9b19b2/coverage-7.14.2-py3-none-any.whl", hash = "sha256:04d92589e481a8b68a005a5a1e0646a91c76f322c397c4635298c57cf63699b5", size = 212325, upload-time = "2026-06-20T14:49:28.991Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9c/a3/3834a5564fe8f32154cd7032400d3c2f9c565b2a373fa671f2bbdad6f634/coverage-7.14.2.tar.gz", hash = "sha256:7a2da3d81cfe17c18038c6d98e6592aa9147d596d056119b0ee612c3c8bd5230", size = 923982, upload-time = "2026-06-20T14:49:30.885Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/56/7f/551ebe25fa3de95ebbd3528b01ffd672b418e9c521b8555f85fb8aca21f8/coverage-7.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b75818e3046e9319143157f3dc4b43679a550c2060a17cbf3e39cc0b552925", size = 220230, upload-time = "2026-06-20T14:47:09.177Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/ec/a444a1a21b46e54298357977d8ab6c388e5755bc79effaf587808fdb405f/coverage-7.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66b08ba4c5cbf0eaa2e9692b203073f198d5d469d8b15d1c7a4854ce7032b2e2", size = 220750, upload-time = "2026-06-20T14:47:11.243Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/38/e5/0dce79f914e31fa0810ab770b06cc638fe5137af259649fafd4daeb2c8e5/coverage-7.14.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:70f266b536c590060b707dddfb6cf9f17e24fd30b992242e774543d256265c43", size = 247487, upload-time = "2026-06-20T14:47:12.564Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/52/bb/aaca2c75ca6a5da71c3f413ac5920fe9f6e1aad387dae52a3315adf313e0/coverage-7.14.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb40cac5b1a6378fdccc99268f1033112ee4636e4fd9aaf240f6930d1fcea12c", size = 249316, upload-time = "2026-06-20T14:47:13.938Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/b3/b45f5edd19ab9f79f7c6a4da2b4d1bd5969e0d7605fe197b60405c7129da/coverage-7.14.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c301fe9990cb5c081bf4881cb498743807c8e0e93fad7b85c02788456492ef8", size = 251182, upload-time = "2026-06-20T14:47:15.144Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/7c/ad5fd04da4565e7c9ad35080a5fb4762ed2d0f4893ac7eff6a1e7364e79f/coverage-7.14.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d67b0462c8a3c3d93033e7c79cacdfc57d08e5220d9115bcb24a23edf5a5900d", size = 253095, upload-time = "2026-06-20T14:47:16.468Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/00/f9/41279b303e8773e1fd9a621a80159c7ed7b643dd9c7e85fd7fc3c88ebdaa/coverage-7.14.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e763087828ee9644f0c89c57f9b75f0a50fdf3e8f5d8fac5cfc351337e89a99", size = 248203, upload-time = "2026-06-20T14:47:17.758Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/ad/205dddd96954fcf7c7f0b509af614c0edc2a547115ad2fb52a8fc9cbaa41/coverage-7.14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6d4da2baab6d96ceedd9176b3c142e1198b0310bc8dc04e18a3caab65c3a322c", size = 249223, upload-time = "2026-06-20T14:47:19.276Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3f/da/46c437176338ece41effbbd07d2941378db04b3e618dce68197d59a870b4/coverage-7.14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ab565a405bfdea61260145d8cc987aa66d1998fd0e0ccd4348008f4e6a39ee33", size = 247226, upload-time = "2026-06-20T14:47:20.613Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/a7/42dfcb471dedb51d366762528e53f232427e8d897b92a9106b4aacec74fc/coverage-7.14.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c13230b688fbb9122251b74daa092175811eb64cb7bd1c98e2c8193dfa2b0bd5", size = 251039, upload-time = "2026-06-20T14:47:22.027Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/29/987df1a9f8843d3b1f50cb47cca0b10c983e84a3b1b9f6965796f07efa4e/coverage-7.14.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:014c83ba1ec97993cfe94e77fe6b56daa76bc0c218b86938971574c28942d044", size = 247497, upload-time = "2026-06-20T14:47:23.374Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8f/1c/919b6624c35161d183c43f57fca52ebcc5a59a7b3fa52fe0d0c3067469f5/coverage-7.14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6caf54ffbf84b30470a8118f275afee9234e616572e4e41bae1dc19198c37294", size = 248100, upload-time = "2026-06-20T14:47:24.621Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/15/acbc7b5a6184f92d4875b820477f0bbb3a87371408f5ef73a575bdd3b8e1/coverage-7.14.2-cp310-cp310-win32.whl", hash = "sha256:4bf9d8a35f77df5638c61b5012ba5225109ec1cc15bc5eb097036b3c3cc939f3", size = 222282, upload-time = "2026-06-20T14:47:25.893Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2f/3f/944e24fdb2e88549b58bfd5a51a3a66481cf21154c7aa1a494597c870125/coverage-7.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:c1f17a8caebe0facd4556b1e0adfe0987c17feebed88e7bb6b5365c45c84c5d6", size = 222910, upload-time = "2026-06-20T14:47:27.331Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/04/d5/d0e511247f84fa88ae7da68403cbd3bf9d2a5fc48f5d6618a6846b275632/coverage-7.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:909f265c8c41f04c824bf741b2601fdcb56cab4bf56e018996b6494192ba0f58", size = 220352, upload-time = "2026-06-20T14:47:28.61Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/03/4a/ecaff6db72e6c1782ca51336e391393f1e9cc6e4412d6c3da8b7d5075adf/coverage-7.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8102deaf911938233f760426e6a5e287388521de95111d5c8de26c8a1028924", size = 220855, upload-time = "2026-06-20T14:47:29.972Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/34/9a/cf950cd8e8df06ee5941276e69f81647005360421be523d5ca18f658e143/coverage-7.14.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:851f49e7bd7d1cdaf328f3133942b252d5e3d3380690131f423cba8e435b87f5", size = 251276, upload-time = "2026-06-20T14:47:31.413Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/08/f973be32c9a095e4bb2d3a7bdcb2f9c117e39d4062471ffffae3623f6c51/coverage-7.14.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04cb445bed86aaf00aaa97d41a8b6e30f100f21e81c34caaec4efc684cb57768", size = 253189, upload-time = "2026-06-20T14:47:32.727Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/96/aa/f3a50952ba553d442d94b793e5dede25d426b02e5e011e9a9dd225c002d3/coverage-7.14.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7471bc920d97c51c37ea8127f13b2adca43c3d78c53313b26a1f428e99d2c254", size = 255299, upload-time = "2026-06-20T14:47:34.019Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/29/9a4c491986f4d637ed64961ae56721661fc21b6b767d280848d0c708756a/coverage-7.14.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:da5057e1bb257c967feee8ba67f3ebf379e801c7717f238b3d8c9caf00fc8f93", size = 257255, upload-time = "2026-06-20T14:47:35.397Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/61/d2a5b48007f6a212f321c36cf5486feb80505d2d00dfb1163aad2da71197/coverage-7.14.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33c0da852e8a40246cd8e20cf3b2fc17ca52a45e9b5f7983c93db26f5d24b87b", size = 251417, upload-time = "2026-06-20T14:47:36.677Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ea/25/8df66ae25b401d4529e1d0617af20d9695d171ea4ffec4ca9dffc5dc37b7/coverage-7.14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f48a85bb437fab7782021c40bfee6b15146928b96960d008ace41b6901a0f21d", size = 252991, upload-time = "2026-06-20T14:47:38.027Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/7b/16bdc9116dd8bf412a421a7227daa65ad9f12bef0685b13c1bd1c12e6d4c/coverage-7.14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f44e7579a769a21d5b5e3166916bfe30ee175aaffff750324cbb11be2dbec5ad", size = 251051, upload-time = "2026-06-20T14:47:39.26Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0a/f8/b7dbed84274dcc69ddb9c0fe72ec1260830473e0d6c299dcf087a0567f7c/coverage-7.14.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:78853ca3c6ca2f012daa2b07dbabbb8db0f09d4dbe8ee828d294b3445d3f4cd8", size = 254817, upload-time = "2026-06-20T14:47:40.995Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/07/4659e6bed01a25a0effb4952e8e75fd157038fe5f2829b0f69c6811c2033/coverage-7.14.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c9c2795ee3692097ff226ab806005d36bb9691fca9b35353542b57ea749cc830", size = 250772, upload-time = "2026-06-20T14:47:42.306Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/f4/45019da4cd6cd1df3042476447449d62a76a201f6b3556aa40ac31bce20b/coverage-7.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2f5cc48a845d755b6db236f8c29c2b54773eb4c7e4ee2ead43812d73718784b0", size = 251679, upload-time = "2026-06-20T14:47:43.703Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/92/e5/76d75fa2ffe0285d3f2608d1bb241fc245cf98fe614d52118427dd6ccdaa/coverage-7.14.2-cp311-cp311-win32.whl", hash = "sha256:9c61cb7eaabcfa609c5bc0f5ff5869d72a2f02f17994e5fba5f971de516f3c82", size = 222445, upload-time = "2026-06-20T14:47:45.137Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/59/696c64547e5c8b9ed31532e9c7a5f9b6474054da93f8ab07f8baf7365c57/coverage-7.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:e715909b0966d1774d8a26e14e2f4a3ae75909dca526901c6306286b2dcbfbdc", size = 222922, upload-time = "2026-06-20T14:47:46.67Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/72/646a28100462996c11b98e27d6786cd61f48100d1479804846a3e1e5bf9b/coverage-7.14.2-cp311-cp311-win_arm64.whl", hash = "sha256:9193f7150937a4fd836b10eaa123e15d98e961d1fabac07e60adf2d4785f888a", size = 222468, upload-time = "2026-06-20T14:47:48.119Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/d9/bdd141aa2c605096a8ef63b8435fd4f5fec78946a3cb7b9145840ec78291/coverage-7.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37c94712e533ea06f0b1e4d934811c520b1914ce0e4da3916220717aa7a86bc6", size = 220528, upload-time = "2026-06-20T14:47:49.652Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/97/d24ae7d2afc62c54a36313d4dedb655c9afbba3003f0f7f1ae81e97af31f/coverage-7.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c050bbc7bba94c77e4ed7438f4fda1babe98ab145691d80aa6f60df934a1468b", size = 220883, upload-time = "2026-06-20T14:47:51.036Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f8/0e/d8f00efd3df0d63e6843ebcbade9e4119d60f5376753c9705d84b014c775/coverage-7.14.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a7af571767a2ee342a171c16fc1b1a07a0bf511606d381703fb7cf397fe49d46", size = 252395, upload-time = "2026-06-20T14:47:52.627Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/1c/ab9510dfe1a16a35a10f90efad0d9a9cf61b9876973752968f2ba882f73f/coverage-7.14.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8b4910cce599cd2438f8da65f5ef199a70a1cdb6ab314926df78271ca5954240", size = 255131, upload-time = "2026-06-20T14:47:54.235Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/dd/70171e9371003b33dc6b20f527ac216ff91bbe5c1088e754eb8950d79193/coverage-7.14.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c33e9e4878972f430b0cc06de3bf2a28d054a9efb4f8426d27de0d9cb81396ff", size = 256246, upload-time = "2026-06-20T14:47:55.61Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0f/80/a68b1dd81d5c011e17fd6ab0d707d33297df1d0c618114b9b750a2219c80/coverage-7.14.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7967ea55c6dea6becba4d5870e2fa0aa4915a8be7ebff1bb79e6207aa75ce8d", size = 258504, upload-time = "2026-06-20T14:47:56.979Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/7b/40baaa946189f5317cd77d484e39b9b0727d02ebada0a12162374f2faee2/coverage-7.14.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1322f237c2979b84096f4239c17828ff17fea6b3bbe96c44381c5f587c44c26", size = 252808, upload-time = "2026-06-20T14:47:58.418Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/05/b19517b09c43d1e8591de6c13178b0c03166c31e1adbebda378e64c66b9a/coverage-7.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77849525340c99f516d793dddbcee16b18d50af892ac43c8de1a6f343d41e3b5", size = 254166, upload-time = "2026-06-20T14:48:00.004Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ae/f5/6e65da5957e041d2094a9b97736628dd80160f1cc007a50790bbb2668c1a/coverage-7.14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef11695493ec3f06f7b2678ca274bcabb4ca04057317df268ddbfd8b05f661a8", size = 252310, upload-time = "2026-06-20T14:48:01.458Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2d/de/01b5274f0db63175b04d9354eff68d2d268b8b57a1b2db7d3dcb1f2c9dbb/coverage-7.14.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8134f0e0723e080d1c27bbe8fc149f0162e429fa1852482150015d0fce83eaf1", size = 256379, upload-time = "2026-06-20T14:48:02.981Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/d6/9a2ffbca41e2f8f86f61e8b78b86afa433ec8cdeac4908ace93a28fe3ff0/coverage-7.14.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:914eead2b843fc357f733b3fe39cc94f1b53d466e8cfe03080b1ed9d24ccfc73", size = 251880, upload-time = "2026-06-20T14:48:04.463Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/ff/20bd54a43c88c08f474e6cb355a97e024e38412873ef0a581629abe1e26f/coverage-7.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4b2d5e847fb7958583b74910cc19e5ec4ece514487385677b26433b2546116e", size = 253753, upload-time = "2026-06-20T14:48:05.99Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/2a/2b3482c30d8344f301d8df6ff232a321f2ab87d5ac97ba21891a68638131/coverage-7.14.2-cp312-cp312-win32.whl", hash = "sha256:e753db9e40dda7302e0ac3e1e6e1325fb7f7b4694f87a7314ab15dd5d57911a7", size = 222584, upload-time = "2026-06-20T14:48:07.361Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/5e/83934ffff147edd313fe925db426e8f7ccad9e4663262eb5c4db4e345658/coverage-7.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:d32e5ca5f16dafb269ee50b60d32b00c704b3f6f78e238105f1d94a3a5f24bf5", size = 223118, upload-time = "2026-06-20T14:48:08.837Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bb/ee/616b4f38a34f076f3045d3eedfa764d34d82e6a6cc6b300acb0f1ff22a98/coverage-7.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:dc366f158e2fb2add9d4e57338ca48f12611024278688ee657eb0b853fcb5de5", size = 222504, upload-time = "2026-06-20T14:48:10.436Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/09/b5b334c27960e7aac0003b96491bada7838dc641099fa64a1a598abf33cd/coverage-7.14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e5f077641a6713ce9d38df9e85d4fb9e008677fc0775cbaeb32ddfc3b319d4ca", size = 220552, upload-time = "2026-06-20T14:48:11.847Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/79/20/879a000c319b4df7b50e4d688c0f7c0f6b5ac9d7b18848cbc00eabf26efe/coverage-7.14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0907f39b49ae818fe8af50aaa0f19afbc8ca164aea0865181ca7af17a3ac690b", size = 220919, upload-time = "2026-06-20T14:48:13.397Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/b7/326dded4371bab60f42215797944a356e4d81a3cee106121c7f7dd531604/coverage-7.14.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734d47669118d75c28981e562d4530ceb77342d31ffef6def5edd5ad4f05d7b", size = 251917, upload-time = "2026-06-20T14:48:14.931Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/eb/14/b3232ba218a0d1a70883d2675f18ff465de9e8e5e3346e81dc2b079838bd/coverage-7.14.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d9a1b5813d00ea6151f6ccf64d1fa16892771dfdda12ba87162d15ec4ea3e1e", size = 254515, upload-time = "2026-06-20T14:48:16.545Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b7/7a/d77bcbee1cad71b42776574114b462225cc9125b4982f43da1b66adc850f/coverage-7.14.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f0a80f4c8ac3f774210b1cc1bc0e31e75502f2818dda9a144ff90e702c4d91d", size = 255749, upload-time = "2026-06-20T14:48:18.214Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/86/86/97377937b29e9e44a1529bb20cb74dbcf80ed9006d87d7e742ff69e44b67/coverage-7.14.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e66f3f22d6c1515ce70f2e7c3e9c6f3ff0ff33480125c9f9c53e8f6508e30f", size = 257882, upload-time = "2026-06-20T14:48:19.7Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/a4/0fc8fe68bc505450bb068a2823ac7797bd8495240ccb8b4a5a1da1ee7e62/coverage-7.14.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a2c37c3114f87ca7f10113756026eecb49656514debad600dcbec21f355ccea", size = 252144, upload-time = "2026-06-20T14:48:21.176Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/4a/450094ddc41ab0d2eb4a0457b3856400ea3329568d1303696e85de099ae6/coverage-7.14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b16a7959d04b1497281c062c180413565c3f3469211d78799ad5b9a75f67796", size = 253882, upload-time = "2026-06-20T14:48:22.701Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/28/2f6ae6d98265d9aa6bac311c4a93403675905b03aca95dc4373080279d75/coverage-7.14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6466c6999545cf00c4c142dfcbbf2db396dc735f005dcf8f91d57e351a79472b", size = 251846, upload-time = "2026-06-20T14:48:24.295Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/6e/707281468400794d52874e8fb5e38ff7578a0ff32ed49fe4fe85f192d0fc/coverage-7.14.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c60915ebb8f562317ba5ff6b8c32e25c0882289b201a9f2fb2987f91efd95d8", size = 256002, upload-time = "2026-06-20T14:48:26.015Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/83/5e963120de4011257a950ce4cfb7fc833ddf3fee19db495268d3dec28154/coverage-7.14.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:33b830850488acbcd358c78a4fecfafe7031667b4da8ddff5546295dc962cdeb", size = 251665, upload-time = "2026-06-20T14:48:27.654Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e9/78/66b482cd525083bcc0bc894c16db79dabac37490065b53b07d6e8ab77202/coverage-7.14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d0f845539230b8269aec902bc978b0cc403f52f002d18a04492efc943404d0bc", size = 253435, upload-time = "2026-06-20T14:48:29.354Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e6/61/0663fb8cb530c8b11819b920109694eee95a3b22960a9495be0200f657f1/coverage-7.14.2-cp313-cp313-win32.whl", hash = "sha256:a8ac51a2e441e9119b9395f4d893fbc4934c64c8ba58be9b9eaa85591249e548", size = 222591, upload-time = "2026-06-20T14:48:31.142Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a6/47/1536d2b009c2848c3682500f497053f4645e70911afe02f594000997831a/coverage-7.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:039b264cdb31c44b48f9821e2afbf8f37df49e0fb837e24a942918b36c567e31", size = 223134, upload-time = "2026-06-20T14:48:32.696Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/28/9a/33ba4f335dd60bb34350318283d784f46018070e67b7d4df7c910ec9d9a0/coverage-7.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:7f2ef591e381cc36b8e53334e1b842c760c520c8a52d01e8626209400e93fe6a", size = 222529, upload-time = "2026-06-20T14:48:34.237Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fc/bc/120390669817ede714ab141ae0a2a73240fd7354aac992c41dc0bd19570f/coverage-7.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7a0d1f026b72d627fa5c8a57cbc86ad209b64aa2a65833c83b290ace5cbee126", size = 220593, upload-time = "2026-06-20T14:48:35.755Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/a3/7f1cfacd76af91e585f7ad689d7168002b444ed2a8ce59f2daaff10089b5/coverage-7.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4d2b86f81c1c9310a7e774e3cc9e927a3d0bf583ecbfa01498dd626930025428", size = 220925, upload-time = "2026-06-20T14:48:37.35Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/10/6514b2525bb672eb8b43703e46d061d694111db21efe7609db722df2233f/coverage-7.14.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d76bdc1f9396ae70a55d050cf9743d88141c62ce0a22a3f627fab1d11c2f8bc6", size = 251974, upload-time = "2026-06-20T14:48:39.109Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/23/b4/4533091541c6620ecd68115bbfa1c61265b775618adef3a5fd137f4582e9/coverage-7.14.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cda36d8e7bfd63b3e44e75163265429caa5d935b672b00f71bccc8c010518c64", size = 254479, upload-time = "2026-06-20T14:48:40.871Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/06/af/e251a143d5d106385dbca696c553afab6b69f7f6bc376a34e089cc0b8b32/coverage-7.14.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0904f3b79d7b845bef0715afe1900da634d12b97f05b9479cb472880ca07cb9c", size = 255824, upload-time = "2026-06-20T14:48:42.608Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9c/53/9e5876e60efbaa79d743d1948a5015ddc05b808db1cd62228acf83e87d43/coverage-7.14.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6795ca4198d6cb7fc2c6163214f6555a6bc5f0ae1e268e76139dec4b37c4499", size = 258139, upload-time = "2026-06-20T14:48:44.263Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/85/5a/d35a4f431fb594e46b81cad4a13b470b017e918f347c1c0b260f7494fa1e/coverage-7.14.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c41e9b60fc0fa57f5d73306417d2f9d668202cca6944f9435878c55a5e7ae213", size = 252002, upload-time = "2026-06-20T14:48:45.961Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/e2/f5b304c8139c606c4f1b230d3a257d0c88edfbbdf06c58364f07625dc45c/coverage-7.14.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419d2aadd5746efc2e9df0f33c05570d8192e6f6a6098ab05acce586f44ce8a5", size = 253832, upload-time = "2026-06-20T14:48:47.582Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/86/bc/bbbd283daa6be4f68aad4ad4066fd39ae98e4174db8c03ab26c5803d6234/coverage-7.14.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1c5d273c5f1411c0d26c4f066c398d4a434b1f97bb5fa409189bedce86d4add4", size = 251799, upload-time = "2026-06-20T14:48:49.42Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/69/8d/0745fceb89c9e5f7dd8ed243d97dc8561b7a95545741e2409d2b34654824/coverage-7.14.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5fe465bc691264adce601527a972990c1174075d86bcbe9968fd20c95e0b1948", size = 256075, upload-time = "2026-06-20T14:48:51.065Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a2/a0/441d9a5255cf021ab41ee00c014a4607d1c72d5e5bef0a4fdaa5be86a907/coverage-7.14.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6fbb61617af1c56f95d53170ae9fa6c9aef6de1abd02fcc50064bfc672efb18d", size = 251612, upload-time = "2026-06-20T14:48:52.653Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/50/37/3d19c5e32d4a529c068eb296abfa3e455bd2c0f9311ecf26280f408ff8e0/coverage-7.14.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e1eff22b831dfd5694989cc1f0789980f18391f614ac67c851af9a8e6d25e9ba", size = 253270, upload-time = "2026-06-20T14:48:54.3Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/b0/54dd13937297518da6d092cc2c39d9340ec2194bdfa92e0a64694d643e23/coverage-7.14.2-cp314-cp314-win32.whl", hash = "sha256:58e91be0a233adef698d3e6be54f10401bb91fd7854c0d4c4d50e0d3711e72f1", size = 222796, upload-time = "2026-06-20T14:48:56.084Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/51/45/7a10e0909919686e335fdd95869cfb222d55243ebff27dc5cf59ca259a1f/coverage-7.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:d8429bf97906bfe6c61f9dbfb3342e0d88120da61939da8bd04f830cc3eab3b8", size = 223285, upload-time = "2026-06-20T14:48:57.729Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2e/03/9cb197eb4b3d1a2eccb2537c226a93c80522c5b8afc5dd93e1993d7bb021/coverage-7.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:13609d9d77249447aa73357b14831b0f3b95f275026c9ff20dd105f981f53a0c", size = 222712, upload-time = "2026-06-20T14:48:59.413Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/3c/e59f498511080d20bf866b0af9eeab820feb91547dae2084cb9bb7fb0e58/coverage-7.14.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9818486c2bac88ae931df7e04905ee29bef49fd218c00f5f02bed4855254a101", size = 221325, upload-time = "2026-06-20T14:49:01.447Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d3/37/8d7955f7e701e69198bd0a0132ea76518c078a635b930a4924e2ccfa70f0/coverage-7.14.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:58055adffabfa243516a197aa9f85f0dd56d905b0fba1a10193269759c29ccb0", size = 221594, upload-time = "2026-06-20T14:49:03.13Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/34/7a/6738e1e1533ce8ec4e2e472696eefdd4723864d7efaa140e433053bf576a/coverage-7.14.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:535747dbc200349d7fb434cffcb28e770f0290f69b225f56dc3803aa7210cdea", size = 262957, upload-time = "2026-06-20T14:49:04.829Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/c4/d1be863cd39e0955904315fece67c5c23e046563f5eea0ceac16c547a759/coverage-7.14.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:420c66e35d85c0ca5dc6a38147d83ef239762542900e5921ebbdb89333c540ea", size = 265081, upload-time = "2026-06-20T14:49:07.018Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/7f/412df3c3c251284a11834287fd6f7e3bb98c528c53e030589e9344a3ef80/coverage-7.14.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2cf17b33773be446a588551ea6a746b2d70dd0bc90dc31f1dd7648975a63c6b", size = 267500, upload-time = "2026-06-20T14:49:08.709Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/54/68/7d0764e83459455384d5c04179ce2d2a837bef01b9ba463079c6e8b31361/coverage-7.14.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adb4a5fef041f7179bb264203add873c147d169cf2f8d0adae89ff2e51271bac", size = 268619, upload-time = "2026-06-20T14:49:10.405Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/14/68/1292164ac70cbcc86ac3982da31a6fbb42bb4bcebf6e5cf73c99cfcfd50d/coverage-7.14.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c012ec357dec9408a83dad5541172a63c5cfa1421709f2e5811480d31ae1b28", size = 262066, upload-time = "2026-06-20T14:49:12.257Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/44/fd6fdf3f63b6e00a1a9230022d072ded5189576001685706aa6524187c65/coverage-7.14.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dacd0ecd08fda3cb2f85b60cabea7da326dcb2fc15fbb23a88830a80144cc9f2", size = 264953, upload-time = "2026-06-20T14:49:14.13Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/39/29/e803fea3da89eaeb5b6b41b3ccd039fe9f3300a900e3803baac1a998529f/coverage-7.14.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f27e980f2feba5dfe7a32b22b125470de69c0bd113c75e16165de909a777f512", size = 262555, upload-time = "2026-06-20T14:49:15.803Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/32/3c/b360e48ac68e3236c04cb83658382e7f5be7efbbec2e1faae3dcca432783/coverage-7.14.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:105c00efb65c863630b2b63cbf7b8267e4da2d44b62284efbb19a03b04c337d4", size = 266289, upload-time = "2026-06-20T14:49:17.962Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/59/12/1ed6d9274d599c586e2d1aa9818765dcdae6bb52aa88afa2fcd868398191/coverage-7.14.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:571173fa04c8e8d6235ab32ae67fecca97777e2e1b4a1a30f3022c34e397c1c1", size = 261402, upload-time = "2026-06-20T14:49:19.708Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/44/17/eb6cf12a4538cda937aefbeabb15377a8a30b377b484e63d31c9da790966/coverage-7.14.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e532f34d42d1a421fa00ed6b7735d14ac2e340256c1bad26a5e1dc1252b0bed7", size = 263715, upload-time = "2026-06-20T14:49:21.427Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8a/ca/4bafdb9d372ab05d6ed3a63e7f00d3195d169d0afea00f617c026e386c19/coverage-7.14.2-cp314-cp314t-win32.whl", hash = "sha256:243971550fb46c3039257f75e65610002d84304c505f609bbd9779e20a653a0a", size = 223103, upload-time = "2026-06-20T14:49:23.24Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/cb/0765dbd9011d2e47315f1da31e62c5fe231f04a6ec8da213e64c4505896d/coverage-7.14.2-cp314-cp314t-win_amd64.whl", hash = "sha256:60fb0ca084a92da96474b8b405a7ea76dfecac3c68db54383e7934b6f3871169", size = 223934, upload-time = "2026-06-20T14:49:25.347Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4e/ce/373dde027ecd0ae54511430fe7569f838d3a0376b70333ba9fd20c76b836/coverage-7.14.2-cp314-cp314t-win_arm64.whl", hash = "sha256:36a0a3f42ed7dfdbca2a69a541519ffd5064a5692152fc0018109e74370d7345", size = 223249, upload-time = "2026-06-20T14:49:27.241Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/5e/a8ba14ceb014f39bd5e3f7077150718c7de61c01ce326bfe7e8eae9b19b2/coverage-7.14.2-py3-none-any.whl", hash = "sha256:04d92589e481a8b68a005a5a1e0646a91c76f322c397c4635298c57cf63699b5", size = 212325, upload-time = "2026-06-20T14:49:28.991Z" },
]
[package.optional-dependencies]
@@ -704,64 +704,64 @@ toml = [
[[package]]
name = "cryptography"
version = "49.0.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" },
- { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" },
- { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" },
- { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" },
- { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" },
- { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" },
- { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" },
- { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" },
- { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" },
- { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" },
- { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" },
- { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" },
- { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" },
- { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" },
- { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" },
- { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" },
- { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" },
- { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" },
- { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" },
- { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" },
- { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" },
- { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" },
- { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" },
- { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" },
- { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" },
- { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" },
- { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" },
- { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" },
- { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" },
- { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" },
- { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" },
- { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" },
- { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" },
- { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" },
- { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" },
- { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" },
- { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
- { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
- { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
- { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" },
- { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" },
- { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" },
- { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" },
- { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" },
- { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" },
]
[[package]]
name = "cyclonedx-bom"
version = "7.3.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "chardet" },
{ name = "cyclonedx-python-lib", extra = ["validation"] },
@@ -770,159 +770,161 @@ dependencies = [
{ name = "pip-requirements-parser" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6c/e1/700aea05811f8988a60636e045914ffb155ce5318879f14b5c9016a12da5/cyclonedx_bom-7.3.0.tar.gz", hash = "sha256:d54080d65731980945de38e52009a5e5711f4a3c31377a3d13af96eaca5fcf3d", size = 4420319, upload-time = "2026-03-30T12:30:08.438Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/e1/700aea05811f8988a60636e045914ffb155ce5318879f14b5c9016a12da5/cyclonedx_bom-7.3.0.tar.gz", hash = "sha256:d54080d65731980945de38e52009a5e5711f4a3c31377a3d13af96eaca5fcf3d", size = 4420319, upload-time = "2026-03-30T12:30:08.438Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c6/f8/285a990b17b44dac4899b14fbeab81a28f3bb828621d4a6c449631249136/cyclonedx_bom-7.3.0-py3-none-any.whl", hash = "sha256:73d7d76b3a28ebadc50eabf424cbcf128785a74b8a59ce7893da2e29cfaab585", size = 60924, upload-time = "2026-03-30T12:30:06.943Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/f8/285a990b17b44dac4899b14fbeab81a28f3bb828621d4a6c449631249136/cyclonedx_bom-7.3.0-py3-none-any.whl", hash = "sha256:73d7d76b3a28ebadc50eabf424cbcf128785a74b8a59ce7893da2e29cfaab585", size = 60924, upload-time = "2026-03-30T12:30:06.943Z" },
]
[[package]]
name = "cyclonedx-python-lib"
-version = "9.1.0"
-source = { registry = "https://pypi.org/simple" }
+version = "11.11.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "license-expression" },
{ name = "packageurl-python" },
{ name = "py-serializable" },
{ name = "sortedcontainers" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/66/fc/abaad5482f7b59c9a0a9d8f354ce4ce23346d582a0d85730b559562bbeb4/cyclonedx_python_lib-9.1.0.tar.gz", hash = "sha256:86935f2c88a7b47a529b93c724dbd3e903bc573f6f8bd977628a7ca1b5dadea1", size = 1048735, upload-time = "2025-02-27T17:23:40.367Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/75/c9/5d0ccdd19bc7d8ab803b90695c1706aa2ea8529685d18e682dc2524d2630/cyclonedx_python_lib-11.11.0.tar.gz", hash = "sha256:4b3194db72b613717f2912447e67ab618c75ff7dcac6c4af3c0e9e1ac617c102", size = 1442983, upload-time = "2026-06-17T11:57:49.055Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/53/f1/f3be2e9820a2c26fa77622223e91f9c504e1581830930d477e06146073f4/cyclonedx_python_lib-9.1.0-py3-none-any.whl", hash = "sha256:55693fca8edaecc3363b24af14e82cc6e659eb1e8353e58b587c42652ce0fb52", size = 374968, upload-time = "2025-02-27T17:23:37.766Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/f3/56ccb2884aaa3db5622368e5191a3384b15f35392aa93df8b2f508c660d2/cyclonedx_python_lib-11.11.0-py3-none-any.whl", hash = "sha256:3049fc83e06a059b5c5907a527625a8ed5073caab10607ed4c9e5503b590fd44", size = 528689, upload-time = "2026-06-17T11:57:47.358Z" },
]
[package.optional-dependencies]
validation = [
- { name = "jsonschema", extra = ["format"] },
+ { name = "jsonschema", extra = ["format-nongpl"] },
{ name = "lxml" },
+ { name = "referencing" },
]
[[package]]
name = "decorator"
version = "5.3.1"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "defusedxml"
version = "0.7.1"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "deprecated"
version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "wrapt" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
]
[[package]]
name = "distlib"
version = "0.4.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
]
[[package]]
name = "docker"
version = "7.1.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "docutils"
version = "0.23"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" },
]
[[package]]
name = "dotty-dict"
version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/6a/ab/88d67f02024700b48cd8232579ad1316aa9df2272c63049c27cc094229d6/dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15", size = 7699, upload-time = "2022-07-09T18:50:57.727Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/ab/88d67f02024700b48cd8232579ad1316aa9df2272c63049c27cc094229d6/dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15", size = 7699, upload-time = "2022-07-09T18:50:57.727Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1a/91/e0d457ee03ec33d79ee2cd8d212debb1bc21dfb99728ae35efdb5832dc22/dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f", size = 7014, upload-time = "2022-07-09T18:50:55.058Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/91/e0d457ee03ec33d79ee2cd8d212debb1bc21dfb99728ae35efdb5832dc22/dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f", size = 7014, upload-time = "2022-07-09T18:50:55.058Z" },
]
[[package]]
name = "dparse"
version = "0.6.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "packaging" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/29/ee/96c65e17222b973f0d3d0aa9bad6a59104ca1b0eb5b659c25c2900fccd85/dparse-0.6.4.tar.gz", hash = "sha256:90b29c39e3edc36c6284c82c4132648eaf28a01863eb3c231c2512196132201a", size = 27912, upload-time = "2024-11-08T16:52:06.444Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/29/ee/96c65e17222b973f0d3d0aa9bad6a59104ca1b0eb5b659c25c2900fccd85/dparse-0.6.4.tar.gz", hash = "sha256:90b29c39e3edc36c6284c82c4132648eaf28a01863eb3c231c2512196132201a", size = 27912, upload-time = "2024-11-08T16:52:06.444Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/56/26/035d1c308882514a1e6ddca27f9d3e570d67a0e293e7b4d910a70c8fe32b/dparse-0.6.4-py3-none-any.whl", hash = "sha256:fbab4d50d54d0e739fbb4dedfc3d92771003a5b9aa8545ca7a7045e3b174af57", size = 11925, upload-time = "2024-11-08T16:52:03.844Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/56/26/035d1c308882514a1e6ddca27f9d3e570d67a0e293e7b4d910a70c8fe32b/dparse-0.6.4-py3-none-any.whl", hash = "sha256:fbab4d50d54d0e739fbb4dedfc3d92771003a5b9aa8545ca7a7045e3b174af57", size = 11925, upload-time = "2024-11-08T16:52:03.844Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "execnet"
version = "2.1.2"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "executing"
version = "2.2.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
]
[[package]]
name = "face"
version = "26.0.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "boltons" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/53/fd/f84f0600bd72953d5a322f0dedbd4f900e2cedab718e6b6a093ae2d16aae/face-26.0.1.tar.gz", hash = "sha256:8183d94bc248baaea855a9f8445f97a22a9988908e60abddccc6e251da77c4c6", size = 51754, upload-time = "2026-06-17T23:14:39.938Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/53/fd/f84f0600bd72953d5a322f0dedbd4f900e2cedab718e6b6a093ae2d16aae/face-26.0.1.tar.gz", hash = "sha256:8183d94bc248baaea855a9f8445f97a22a9988908e60abddccc6e251da77c4c6", size = 51754, upload-time = "2026-06-17T23:14:39.938Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0a/24/0159c48d19c8b05e6969ee809bbbecc5a5c863f7e38c9327e2c63cb06f0f/face-26.0.1-py3-none-any.whl", hash = "sha256:ab0a83c37c9789dce658a67a9a80eafaa113c9ec37c5a9d950ff5480542a062d", size = 57572, upload-time = "2026-06-17T23:14:38.711Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0a/24/0159c48d19c8b05e6969ee809bbbecc5a5c863f7e38c9327e2c63cb06f0f/face-26.0.1-py3-none-any.whl", hash = "sha256:ab0a83c37c9789dce658a67a9a80eafaa113c9ec37c5a9d950ff5480542a062d", size = 57572, upload-time = "2026-06-17T23:14:38.711Z" },
]
[[package]]
name = "fastapi"
version = "0.138.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "annotated-doc" },
{ name = "pydantic" },
@@ -930,45 +932,45 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5b/58/ff455d9fe47c60abadb34b9e05a304b1f05f5ab8000ac01565156b6f5e43/fastapi-0.138.0.tar.gz", hash = "sha256:d445a4877636ad191e7053e08c9bf98cb921a6756776848400bb773d1740c061", size = 419240, upload-time = "2026-06-20T01:18:05.259Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/58/ff455d9fe47c60abadb34b9e05a304b1f05f5ab8000ac01565156b6f5e43/fastapi-0.138.0.tar.gz", hash = "sha256:d445a4877636ad191e7053e08c9bf98cb921a6756776848400bb773d1740c061", size = 419240, upload-time = "2026-06-20T01:18:05.259Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6c/ff/8496d9847a5fedae775eb49460722d3efaa80487854273e9647ae876218c/fastapi-0.138.0-py3-none-any.whl", hash = "sha256:b6f54fd1bd72c80b0f899f172c61a600f6f7af9b43d4d772a018f35624048cb0", size = 126779, upload-time = "2026-06-20T01:18:03.483Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/ff/8496d9847a5fedae775eb49460722d3efaa80487854273e9647ae876218c/fastapi-0.138.0-py3-none-any.whl", hash = "sha256:b6f54fd1bd72c80b0f899f172c61a600f6f7af9b43d4d772a018f35624048cb0", size = 126779, upload-time = "2026-06-20T01:18:03.483Z" },
]
[[package]]
name = "filelock"
version = "3.29.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" },
]
[[package]]
name = "fqdn"
version = "1.5.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
]
[[package]]
name = "ghp-import"
version = "2.1.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "git-changelog"
version = "2.9.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jinja2" },
{ name = "packaging" },
@@ -977,252 +979,273 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/01/7f/17e58ec656fd79d4a3b02517923e24a93b28fe2e4518568aedb4ce038fb9/git_changelog-2.9.4.tar.gz", hash = "sha256:06a38a0d174a069b760430ec41493bf53307be13f16b0e48ce379be1646ea9a0", size = 92601, upload-time = "2026-04-29T14:01:39.575Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/01/7f/17e58ec656fd79d4a3b02517923e24a93b28fe2e4518568aedb4ce038fb9/git_changelog-2.9.4.tar.gz", hash = "sha256:06a38a0d174a069b760430ec41493bf53307be13f16b0e48ce379be1646ea9a0", size = 92601, upload-time = "2026-04-29T14:01:39.575Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/a6/fb6713ce5c128078492b5ce691bd27b8a7c6f2ad84aca99d3b847ab1721d/git_changelog-2.9.4-py3-none-any.whl", hash = "sha256:c19a099640518fbbc545cb240250cf8129e55e668d8374ba8061ea5456da204b", size = 40604, upload-time = "2026-04-29T14:01:38.195Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ec/a6/fb6713ce5c128078492b5ce691bd27b8a7c6f2ad84aca99d3b847ab1721d/git_changelog-2.9.4-py3-none-any.whl", hash = "sha256:c19a099640518fbbc545cb240250cf8129e55e668d8374ba8061ea5456da204b", size = 40604, upload-time = "2026-04-29T14:01:38.195Z" },
]
[[package]]
name = "gitdb"
version = "4.0.12"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "22.1.0"
-source = { registry = "https://pypi.org/simple" }
+version = "25.12.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "attrs" },
{ name = "boltons" },
{ name = "face" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3f/d1/69432deefa6f5283ec75b246d0540097ae26f618b915519ee3824c4c5dd6/glom-22.1.0.tar.gz", hash = "sha256:1510c6587a8f9c64a246641b70033cbc5ebde99f02ad245693678038e821aeb5", size = 189738, upload-time = "2022-01-24T09:34:04.874Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/27/e8/68e274b2a30e1fdfd25bdc27194382be3f233929c8f727c0440d58ac074f/glom-22.1.0-py2.py3-none-any.whl", hash = "sha256:5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772", size = 100687, upload-time = "2022-01-24T09:34:02.391Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "graphql-core"
version = "3.2.11"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/4d/90/f2aff026ab4aebd80eb71905106a0885f4cfde85dcf965543f45bed0d9ee/graphql_core-3.2.11.tar.gz", hash = "sha256:e7e156d10beb127cab5c89ff0da71416fc73d27c484a4757d3b2d35633774802", size = 528407, upload-time = "2026-06-05T13:45:22.915Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4d/90/f2aff026ab4aebd80eb71905106a0885f4cfde85dcf965543f45bed0d9ee/graphql_core-3.2.11.tar.gz", hash = "sha256:e7e156d10beb127cab5c89ff0da71416fc73d27c484a4757d3b2d35633774802", size = 528407, upload-time = "2026-06-05T13:45:22.915Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/00/15/b92b4e1d88d02c6eff9733c9eea21846ab435cc4d813d84ccc5d335955df/graphql_core-3.2.11-py3-none-any.whl", hash = "sha256:0b3e35ff41e9adba53021ab0cef475eb18f57c7f53f0f2ca55567fbf3c537ea0", size = 214879, upload-time = "2026-06-05T13:45:21.245Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/00/15/b92b4e1d88d02c6eff9733c9eea21846ab435cc4d813d84ccc5d335955df/graphql_core-3.2.11-py3-none-any.whl", hash = "sha256:0b3e35ff41e9adba53021ab0cef475eb18f57c7f53f0f2ca55567fbf3c537ea0", size = 214879, upload-time = "2026-06-05T13:45:21.245Z" },
]
[[package]]
name = "greenlet"
version = "3.5.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/dd/8b/befc3cb36965f397d87e86fb3b00e3ec0dc67c1ecb0986d7f54ee528f018/greenlet-3.5.2.tar.gz", hash = "sha256:c1b906220d83c140361cdd12eef970fb5881a168b98ee58a43786426173da14c", size = 199243, upload-time = "2026-06-17T20:19:01.317Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2b/3a/cd99db55dc908568f6b91845747b98b3b17a06052fa1803d091dc91da27d/greenlet-3.5.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9df9daae96848508450011d0d86ed7c95f8829a354ce438284a77b24896fd1f8", size = 285626, upload-time = "2026-06-17T17:33:33.231Z" },
- { url = "https://files.pythonhosted.org/packages/ce/09/fd997a19cbb97641233c7d5f8fc89314c132be2c8867c4f14beff979996f/greenlet-3.5.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01e32e9d2b1714a2b06184cb3071ff2a2fd9bc7d065e39198ab21f7253dad421", size = 601821, upload-time = "2026-06-17T18:07:16.756Z" },
- { url = "https://files.pythonhosted.org/packages/7d/b0/62abd204addd913ad9856e091f5d8baaedc7c85df151f22f093b8a207c20/greenlet-3.5.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0488ca77c94da5e09d1d9958f98b58cebba1b8fd9664c24898499133de927574", size = 615044, upload-time = "2026-06-17T18:29:39.344Z" },
- { url = "https://files.pythonhosted.org/packages/34/67/ceaab731b51611a8238b0af2d4abb4fd727ec09b16cd499fca5295603f46/greenlet-3.5.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d9e19257794e28821c9ebd5e23f86d7c267cd9d390089374f068d2049f949e3", size = 615176, upload-time = "2026-06-17T17:39:25.134Z" },
- { url = "https://files.pythonhosted.org/packages/1c/40/51a0ee73b72a7e4a65b54433316bbd7b3b7902a585310cd4e3051d411ee3/greenlet-3.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bf493b3c1c0a2324c49b0472e2280ba4665f3510d8115f6f807759a6163b15f7", size = 1574580, upload-time = "2026-06-17T18:22:09.082Z" },
- { url = "https://files.pythonhosted.org/packages/41/d3/a3a2163b1fe73042d3e72cfcb9920f2481d5188a1df2645587a9b83a903f/greenlet-3.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:561dd919c02236a613fbf226791cbd77ee5002cbd5cb7e838869aa3ac7a71e16", size = 1641192, upload-time = "2026-06-17T17:40:04.234Z" },
- { url = "https://files.pythonhosted.org/packages/95/a3/b4d83fb451e2f7266cb45ccef23857f8a800e0a5d9a73263fafdf7ba7904/greenlet-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:049827baab63dda8ab8ec5a6d07fc6eb0f418319cfc757fc8737a605e99ca1ad", size = 238247, upload-time = "2026-06-17T17:34:54.794Z" },
- { url = "https://files.pythonhosted.org/packages/21/68/371ee6dad168be3386c46030bedaa8e3e7e3cf3d203621d4529e78ff36ef/greenlet-3.5.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d7792398872f89466c6671d5d193537eff163ecf7fac78d82e6ddc25017fb4f5", size = 286925, upload-time = "2026-06-17T17:33:17.928Z" },
- { url = "https://files.pythonhosted.org/packages/26/16/ed5706c26b4d26f3fabceb79abca992654eac8b0fa435def2ac6dbd92122/greenlet-3.5.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:711028c953cd6ce5dc01bbb5a1747e3ad6bd8b2f7ded73778bb936e8dab9e3b6", size = 606036, upload-time = "2026-06-17T18:07:18.538Z" },
- { url = "https://files.pythonhosted.org/packages/8e/32/f9c77093af9f5f96615922b7e3fe3690a9faff02adb89f1d74e21578b147/greenlet-3.5.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5eba55076d79e8a5176e6925295cfb901ebc95dae493342ede22230f75d8bee2", size = 617821, upload-time = "2026-06-17T18:29:41.317Z" },
- { url = "https://files.pythonhosted.org/packages/bd/d4/642833e778c17d32b5cabb793e14ce7364c55952462fc506fecdee55d485/greenlet-3.5.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1c1e5ad80f1f38ea479b83b39dccb20874cfe9ad5e52f87225fa294ba4d39a1", size = 616877, upload-time = "2026-06-17T17:39:26.564Z" },
- { url = "https://files.pythonhosted.org/packages/d3/cc/7120f83e78b8be3cf7acbe2306b3b7bd2cbf99f5ad12e85e2f05d7b31961/greenlet-3.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e194b996aa1b89d933cfe136e5eb39b22a8b72ba59d376ef39a55bca4dbf47f", size = 1577274, upload-time = "2026-06-17T18:22:10.692Z" },
- { url = "https://files.pythonhosted.org/packages/fa/d8/05a0074ee485dd51c320fd706fd7ed48006b9cad3443092d7df1a655f0d2/greenlet-3.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e554809538bd4867f24421b43abde170f9c9b8192149b30df5e164bcac6124f", size = 1643566, upload-time = "2026-06-17T17:40:05.452Z" },
- { url = "https://files.pythonhosted.org/packages/35/fe/9fe2060bdeece682e38d381184ae66045b48ed183c107ab3f88b9886a630/greenlet-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:e063263ce9047878480d7e536012fc8b7c8e1922989eb5f03b9ab998a2ee7b7e", size = 238643, upload-time = "2026-06-17T17:37:03.039Z" },
- { url = "https://files.pythonhosted.org/packages/41/13/a9db72f5b6b700977ebd371d6a1f2984a08838357de924fcd5571607b1bf/greenlet-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:a3f76a94e2d6e1fee8f302265679d8cc47d71a203936dd03c6e2ace0f9cfd46d", size = 237135, upload-time = "2026-06-17T17:34:34.14Z" },
- { url = "https://files.pythonhosted.org/packages/3f/7a/6bc2a7835731387ed303b9390ce68a116ab053df05450a59181239200454/greenlet-3.5.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:76dae33e97b52743a19210931ee3e78a88fe1438bc2fc4ee5e7512d289bfad4f", size = 288351, upload-time = "2026-06-17T17:36:17.019Z" },
- { url = "https://files.pythonhosted.org/packages/57/1b/bd98062fcef6d0e9d0873ab6f2d029772e6ea342972ae43275bd6177900f/greenlet-3.5.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30252d191d6959df1d040b559a38fc017139606c5ecc2ad00416557c0355d742", size = 604273, upload-time = "2026-06-17T18:07:20.296Z" },
- { url = "https://files.pythonhosted.org/packages/25/e6/fe392c522bf45d976abe7db2793f6ef4e87b053ebb869deeaae46aeb54da/greenlet-3.5.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1adc23c50f22b0f5979521909a8360ab4a3d3bef8b641ce633a04cf1b1c967ea", size = 616536, upload-time = "2026-06-17T18:29:43.205Z" },
- { url = "https://files.pythonhosted.org/packages/68/4a/399ff81fa93a19d6a9df394cef0355f082dbc19ad41aba9593cd0ad444e2/greenlet-3.5.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f052fff492c52fdfa99bd3b3c1389a53de37dae76a0562741417f0d018f02b3", size = 613749, upload-time = "2026-06-17T17:39:28.148Z" },
- { url = "https://files.pythonhosted.org/packages/a5/75/f519593f12ad43d08e28c03a95cfe2eeae011707dbc9dab0c4a263ce90f9/greenlet-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:120b77c2a18ebf629c3a7886f68c6d01e065654844ad468f15bb93ace66f2094", size = 1573725, upload-time = "2026-06-17T18:22:12.023Z" },
- { url = "https://files.pythonhosted.org/packages/f1/bc/bc1ea4b0754c6c51bbf9d94677b0b1f7fbda8cbb404e44a896854fc0a940/greenlet-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a850f6224088ef7dcc70f1a545cb6b3d119c35d6dca63b925b9f35da0635cdad", size = 1638132, upload-time = "2026-06-17T17:40:06.971Z" },
- { url = "https://files.pythonhosted.org/packages/36/c0/f0f5a34247df60de285f75f22e57f14027f4b3c43820981854b5b643ca6d/greenlet-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:89da99ee8345b458ea2f16831dad31c88ddcdec454b48704d569a0b8fb28f146", size = 239393, upload-time = "2026-06-17T17:33:47.09Z" },
- { url = "https://files.pythonhosted.org/packages/09/17/a8544e165445f30aea67a8d9cf2786d2bb0eb1b0e0d224b4d9bd80e2d587/greenlet-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:ca92411942154023c65851e6077d8ca0d00f19de5fa80bb2c6f196ff6c920ba9", size = 237723, upload-time = "2026-06-17T17:36:47.776Z" },
- { url = "https://files.pythonhosted.org/packages/d0/3c/bb37b9d40d65b0741a8b040ca5c307034d0a9822994dff5f825c88dd7a6b/greenlet-3.5.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0629377725977252159de1ebd3c6e49c170a63856e585446797bb3d66d4d9c34", size = 287178, upload-time = "2026-06-17T17:35:25.132Z" },
- { url = "https://files.pythonhosted.org/packages/f0/a6/0c5902393f492f8ceb19d0b5cf139284e3a11b333a049739643b1036b6f8/greenlet-3.5.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2ddf9eddc617681108dd071b3feabf3f4a4cd64846254aec4d4ceda098b639a", size = 606900, upload-time = "2026-06-17T18:07:21.692Z" },
- { url = "https://files.pythonhosted.org/packages/d8/7c/42899c31d4b87148ae4e3f87f63e13398824be6241f4dde42ded95768a34/greenlet-3.5.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f41feb9f2b59e2e61ac9bea4e344ddd9396bf3cacb2583f73a3595ed7df6f8e7", size = 619265, upload-time = "2026-06-17T18:29:44.837Z" },
- { url = "https://files.pythonhosted.org/packages/d3/52/4ff8c98d3cfe62b4515f8584ae14510a58f35c549cc5292b78d9b7a40b70/greenlet-3.5.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09201fa698768db245920b00fdc86ee3e73540f01ca6db162be9632642e1a473", size = 616187, upload-time = "2026-06-17T17:39:29.473Z" },
- { url = "https://files.pythonhosted.org/packages/c9/a6/269c8bf9aefc13361ce1088f0e392b154cb21005de7862e42b5d782b81fd/greenlet-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1759fa4f14c398508cf20dc8037de55cc23ae8bd14c185c2718257837195ca5", size = 1573778, upload-time = "2026-06-17T18:22:13.497Z" },
- { url = "https://files.pythonhosted.org/packages/1f/9b/391d015cbc6323e81b14c02cf825fdca7e0049c9bb489bf4ac72883118ba/greenlet-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9318cdeb9abdbfdd8bc8464ee4a06dffde2c7846e1def138365a6240ab2c9a5", size = 1638092, upload-time = "2026-06-17T17:40:08.163Z" },
- { url = "https://files.pythonhosted.org/packages/49/53/5b4df711f4356c62e85d9f819d87966d526d1cfb32bae49a8f7d6fc36ea4/greenlet-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:2c3b3311af72b3d3b03cc0f1ffd11f072e834be5d0444105cf715fc44434e39c", size = 239352, upload-time = "2026-06-17T17:38:51.593Z" },
- { url = "https://files.pythonhosted.org/packages/bb/b6/18efc3a329ec035c3f344b8f2b60356451950ddf9b7b64ff00023778a1dd/greenlet-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:f9bbd6216c45a563c2a61e478e038b439d9f248bde44f775ea37d339da643af4", size = 237635, upload-time = "2026-06-17T17:35:36.632Z" },
- { url = "https://files.pythonhosted.org/packages/c7/89/aaafc8e14de4ac882e02ccb963225329b0e8578aba4365e71eb678e45722/greenlet-3.5.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1c31219badba285858ba8ed117f403dea7fafee6bade9a1991875aae530c3ceb", size = 287676, upload-time = "2026-06-17T17:33:31.514Z" },
- { url = "https://files.pythonhosted.org/packages/b8/fc/2308249206c12ac70de7b9a00970f84f07d10b3cd60e05d2fbcaa84124e8/greenlet-3.5.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f96ed6f4adc1066954ae95f45717657cb67468ef3b89e9a3632e14a625a8f39", size = 653552, upload-time = "2026-06-17T18:07:23.493Z" },
- { url = "https://files.pythonhosted.org/packages/7c/24/47730d1f8f1336b9b089237521ed7a26eee997065dcb4cab81cdca333abc/greenlet-3.5.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5795e883e915333c0d5648faaa691857fbc7180136883edc377f50f0d509c2a8", size = 665756, upload-time = "2026-06-17T18:29:46.616Z" },
- { url = "https://files.pythonhosted.org/packages/99/69/d6c99db15dc0b5e892ac3cc7b942c8b21f4a9cc3bd9ea0bc3b0f339ffbd4/greenlet-3.5.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26aed8d9503ca78889141a9739d71b383efea5f472a7c522b5410f7eb2a1b163", size = 663228, upload-time = "2026-06-17T17:39:31.073Z" },
- { url = "https://files.pythonhosted.org/packages/4f/88/9e603f448e2bc107c883e95817b980fb9b45ba6aea0299b2e9978124bea2/greenlet-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dbebc038fcdda8f8f21cce985fd04e34e0f42007e7fc7ab7ad285caf77974b95", size = 1620723, upload-time = "2026-06-17T18:22:14.817Z" },
- { url = "https://files.pythonhosted.org/packages/11/91/26da17e3777858c16fdb8d020a4c68f3a03cb92f238de8f5351d5d5186e9/greenlet-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a207023f1cf8695fd82580b8099c09c5809be18bc2282362cdfb965dd884a317", size = 1684227, upload-time = "2026-06-17T17:40:09.536Z" },
- { url = "https://files.pythonhosted.org/packages/2d/44/b3a11f7aa34cb38f1b7f3df8bcd9fcd09bac9d342c2a2c9b8686c804bcd2/greenlet-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:c674a1dd4fe41f6a93febe7ab366ceabf15080ea31a9307811c56dac5f435f73", size = 240257, upload-time = "2026-06-17T17:35:23.359Z" },
- { url = "https://files.pythonhosted.org/packages/de/e3/3b62145fe917311732041a258adb218248add00542e3131c48bd047fbed5/greenlet-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3c417cd6c593bbbef6f7aa31a79f37d3db7d18832fc56b694a2150130bde784e", size = 239038, upload-time = "2026-06-17T17:37:56.792Z" },
- { url = "https://files.pythonhosted.org/packages/47/ac/d3bad483e9f6cd1848604fdffa32cac25846dd6dfcec0e6f81c790185518/greenlet-3.5.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a96457a30384de52d9c5d2fd33abf6c1daae3db392cd556738f408b1a79a1cf0", size = 295668, upload-time = "2026-06-17T17:36:02.293Z" },
- { url = "https://files.pythonhosted.org/packages/00/e9/3a7e557b895fd0469b00cd0b2bd498ba950e8bfdf6d7adeecf2c5e4130a6/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4af5d4961818ab651d09c1448a03b1ba2a1726a076266ebb62330bab9f3238c", size = 652820, upload-time = "2026-06-17T18:07:24.95Z" },
- { url = "https://files.pythonhosted.org/packages/78/67/6225d5c5e4afc04be0fd161eec82e4b72017e8a100d222f25d7b42b0140d/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1789a6244ea1ba61fd4386c9a6a31873e9b0234762103364be98ef87dcb19f3", size = 658697, upload-time = "2026-06-17T18:29:48.365Z" },
- { url = "https://files.pythonhosted.org/packages/fa/99/6324b8ef916dcaddccb340b304c992ca3f947614ce0f2685d438187300b8/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3be00501fb4a8c37f6b4b3c4773808ceb26ea65c7ea64fd5735d0f330b3786de", size = 656436, upload-time = "2026-06-17T17:39:32.509Z" },
- { url = "https://files.pythonhosted.org/packages/a9/ee/f5bf9daac27c5e1b011965f64b5630a32b415daf7381b312943629e12c2a/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1d554cd96841a68d464d75a3736f8e87408a7b02b1930a75fa32feb408ad62f8", size = 1617193, upload-time = "2026-06-17T18:22:16.252Z" },
- { url = "https://files.pythonhosted.org/packages/8a/21/b05d5b12715bda92ce27c118d64971d21e9b8f3563ed959a7d271e2d4223/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3dff6cd3aac35f6cd3fc23460105acf576f5faf6c378de0bc088bf37c913864a", size = 1677512, upload-time = "2026-06-17T17:40:10.771Z" },
- { url = "https://files.pythonhosted.org/packages/b8/97/1b8f1314b868041b327dc1051603e8142b826480cb0ecb8a7b7632aee9c4/greenlet-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:36cfea2aa075d544617176b2e84450480f0797070ad8799a8c41ada2fe449d32", size = 243145, upload-time = "2026-06-17T17:34:37.502Z" },
- { url = "https://files.pythonhosted.org/packages/36/07/1b5311775e04c718a118c504d7a3a312430e2a1bd1347226aff4774e4549/greenlet-3.5.2-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:a0314aa832c94633355dc6f3ee54f195159533355a323f26926fc63b98b2ccbb", size = 288315, upload-time = "2026-06-17T17:34:34.04Z" },
- { url = "https://files.pythonhosted.org/packages/ed/cc/6abcd2a486b58b9f77b7a93b690d59cb2c11a5906ed2ad4c63c7b9c1113d/greenlet-3.5.2-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24c59cb7db9d5c694cb8fd0c76eef8e456b2123afdfa7e4b8f2a67a0860d7682", size = 659130, upload-time = "2026-06-17T18:07:26.354Z" },
- { url = "https://files.pythonhosted.org/packages/f2/12/f4aaad6d3d383233f700ab322568a4f29f2c701a4861d85f4811d99689b2/greenlet-3.5.2-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7bb811753703739ad318112f16eccfaabdac050037b6d092debaa8b23566b4ce", size = 669724, upload-time = "2026-06-17T18:29:50.13Z" },
- { url = "https://files.pythonhosted.org/packages/91/2a/a089811fc31c6bf8742f40a4e73470d6d401cef18e4314eb20dc399b377c/greenlet-3.5.2-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d78b5c1c178dad90447f1b8452262709d3eef4c98f825569e74c9d0b2260ac9", size = 668089, upload-time = "2026-06-17T17:39:33.808Z" },
- { url = "https://files.pythonhosted.org/packages/0f/1c/2f47c7d5fcfa98a62b705bf9a0505d86f4563c0d81cab1f7159ff1e743b7/greenlet-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0977af2df83136f81c1f76e76d4e2fe7d0dc56ea9c101a86af26a95190b9ca32", size = 1625684, upload-time = "2026-06-17T18:22:17.664Z" },
- { url = "https://files.pythonhosted.org/packages/b9/bf/661dd24624f70b7b32972d7693d0344ecde10278f647d7b828baf739899c/greenlet-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f9ed777c6891d8253e54468576f55e27f8fc1a662a664f946a191003574c0a74", size = 1688043, upload-time = "2026-06-17T17:40:12.403Z" },
- { url = "https://files.pythonhosted.org/packages/60/49/d9bde1d15a21296b3b521fe083eb8aabd54ac05d15de9832918f3d639543/greenlet-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:c0ea4eb3de23f0bac1d75205e10ccfa9b418b17b01a2d7bf19e3b69dda08900a", size = 240531, upload-time = "2026-06-17T17:35:47.448Z" },
- { url = "https://files.pythonhosted.org/packages/7f/4d/86d7768bd53e9907de0333df215c2018cd01a593b3715cbd79aa82dd94b7/greenlet-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:7a7bfc200be40d04961d7e80e8337d726c0c1a50777e588123c3ed8ba731dcb9", size = 239579, upload-time = "2026-06-17T17:39:39.954Z" },
- { url = "https://files.pythonhosted.org/packages/92/15/907be5e8900901039bae752fa9a31c03a3c1e064833f35a4e49449184581/greenlet-3.5.2-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:98a52d6a50d4deaba304331d83ee3e10ebbdc1517fcca40b2715d1de4534065c", size = 296697, upload-time = "2026-06-17T17:37:15.887Z" },
- { url = "https://files.pythonhosted.org/packages/95/5c/08c57be575c3d6a3c023bbf22144a1c7dc6ed4d134527bb36ded4dbf04a8/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1587ff8b58fdf806993ed1490a06ac19c22d47b219c68b30954380029045d8d4", size = 656710, upload-time = "2026-06-17T18:07:28.046Z" },
- { url = "https://files.pythonhosted.org/packages/8c/d0/749f917bdc9fc90fceea4aa65fbf6556e617a50714d1496bdc8ad190bb36/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:feb721811d2754bfd16b48de151dd6b1f222c048e625151f2ca44cfdfd69f59c", size = 662629, upload-time = "2026-06-17T18:29:51.728Z" },
- { url = "https://files.pythonhosted.org/packages/5a/a5/68cefae3a07f6d0093a490cf28ab604f14578f3e60205a2a2b2d5cd70af2/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fe6062b1f35534e1e8fb28dfed406cf4eeff3e0bca3a0d9f8ff69f20a4abb00", size = 660147, upload-time = "2026-06-17T17:39:35.068Z" },
- { url = "https://files.pythonhosted.org/packages/d2/6b/b9156d8397e4750220f54c7c5c34650f1e740a8d2f66eab9cfd1b7b53b69/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b4ac902af825cbac8e9b2fccab8122236fd2ba6c8b71a080116d2c2ec72671b1", size = 1621675, upload-time = "2026-06-17T18:22:18.873Z" },
- { url = "https://files.pythonhosted.org/packages/b0/e3/d3250f4fa01c211a93d04e34fded63187e648dbec17b9b1a14d388040593/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6f1e473c06ae8be00c9034c2bb10fa277b08a93287e3111c395b839f01d27e1f", size = 1680577, upload-time = "2026-06-17T17:40:14.055Z" },
- { url = "https://files.pythonhosted.org/packages/55/ba/eaee8bda4419770d7096b5a009ebff0ab20a2a28cdd83c4b591bfdf36fa9/greenlet-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:3c2315045f9983e2e50d7e89d95405c21bddb8745f2da4487bc080ab3525f904", size = 243482, upload-time = "2026-06-17T17:37:34.741Z" },
- { url = "https://files.pythonhosted.org/packages/37/45/f794a81c91e9942c61f9110bd1f9a38a0ea565eab57f8b08cd53d3131e48/greenlet-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:db548d5ab6c2a8ead82c013f875090d79b5d7d2b67fc513934ce6cf66492ad7f", size = 242062, upload-time = "2026-06-17T17:35:39.814Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/8b/befc3cb36965f397d87e86fb3b00e3ec0dc67c1ecb0986d7f54ee528f018/greenlet-3.5.2.tar.gz", hash = "sha256:c1b906220d83c140361cdd12eef970fb5881a168b98ee58a43786426173da14c", size = 199243, upload-time = "2026-06-17T20:19:01.317Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2b/3a/cd99db55dc908568f6b91845747b98b3b17a06052fa1803d091dc91da27d/greenlet-3.5.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9df9daae96848508450011d0d86ed7c95f8829a354ce438284a77b24896fd1f8", size = 285626, upload-time = "2026-06-17T17:33:33.231Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/09/fd997a19cbb97641233c7d5f8fc89314c132be2c8867c4f14beff979996f/greenlet-3.5.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01e32e9d2b1714a2b06184cb3071ff2a2fd9bc7d065e39198ab21f7253dad421", size = 601821, upload-time = "2026-06-17T18:07:16.756Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7d/b0/62abd204addd913ad9856e091f5d8baaedc7c85df151f22f093b8a207c20/greenlet-3.5.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0488ca77c94da5e09d1d9958f98b58cebba1b8fd9664c24898499133de927574", size = 615044, upload-time = "2026-06-17T18:29:39.344Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/34/67/ceaab731b51611a8238b0af2d4abb4fd727ec09b16cd499fca5295603f46/greenlet-3.5.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d9e19257794e28821c9ebd5e23f86d7c267cd9d390089374f068d2049f949e3", size = 615176, upload-time = "2026-06-17T17:39:25.134Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/40/51a0ee73b72a7e4a65b54433316bbd7b3b7902a585310cd4e3051d411ee3/greenlet-3.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bf493b3c1c0a2324c49b0472e2280ba4665f3510d8115f6f807759a6163b15f7", size = 1574580, upload-time = "2026-06-17T18:22:09.082Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/d3/a3a2163b1fe73042d3e72cfcb9920f2481d5188a1df2645587a9b83a903f/greenlet-3.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:561dd919c02236a613fbf226791cbd77ee5002cbd5cb7e838869aa3ac7a71e16", size = 1641192, upload-time = "2026-06-17T17:40:04.234Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/95/a3/b4d83fb451e2f7266cb45ccef23857f8a800e0a5d9a73263fafdf7ba7904/greenlet-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:049827baab63dda8ab8ec5a6d07fc6eb0f418319cfc757fc8737a605e99ca1ad", size = 238247, upload-time = "2026-06-17T17:34:54.794Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/21/68/371ee6dad168be3386c46030bedaa8e3e7e3cf3d203621d4529e78ff36ef/greenlet-3.5.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d7792398872f89466c6671d5d193537eff163ecf7fac78d82e6ddc25017fb4f5", size = 286925, upload-time = "2026-06-17T17:33:17.928Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/16/ed5706c26b4d26f3fabceb79abca992654eac8b0fa435def2ac6dbd92122/greenlet-3.5.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:711028c953cd6ce5dc01bbb5a1747e3ad6bd8b2f7ded73778bb936e8dab9e3b6", size = 606036, upload-time = "2026-06-17T18:07:18.538Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/32/f9c77093af9f5f96615922b7e3fe3690a9faff02adb89f1d74e21578b147/greenlet-3.5.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5eba55076d79e8a5176e6925295cfb901ebc95dae493342ede22230f75d8bee2", size = 617821, upload-time = "2026-06-17T18:29:41.317Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/d4/642833e778c17d32b5cabb793e14ce7364c55952462fc506fecdee55d485/greenlet-3.5.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1c1e5ad80f1f38ea479b83b39dccb20874cfe9ad5e52f87225fa294ba4d39a1", size = 616877, upload-time = "2026-06-17T17:39:26.564Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d3/cc/7120f83e78b8be3cf7acbe2306b3b7bd2cbf99f5ad12e85e2f05d7b31961/greenlet-3.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e194b996aa1b89d933cfe136e5eb39b22a8b72ba59d376ef39a55bca4dbf47f", size = 1577274, upload-time = "2026-06-17T18:22:10.692Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/d8/05a0074ee485dd51c320fd706fd7ed48006b9cad3443092d7df1a655f0d2/greenlet-3.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e554809538bd4867f24421b43abde170f9c9b8192149b30df5e164bcac6124f", size = 1643566, upload-time = "2026-06-17T17:40:05.452Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/fe/9fe2060bdeece682e38d381184ae66045b48ed183c107ab3f88b9886a630/greenlet-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:e063263ce9047878480d7e536012fc8b7c8e1922989eb5f03b9ab998a2ee7b7e", size = 238643, upload-time = "2026-06-17T17:37:03.039Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/13/a9db72f5b6b700977ebd371d6a1f2984a08838357de924fcd5571607b1bf/greenlet-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:a3f76a94e2d6e1fee8f302265679d8cc47d71a203936dd03c6e2ace0f9cfd46d", size = 237135, upload-time = "2026-06-17T17:34:34.14Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3f/7a/6bc2a7835731387ed303b9390ce68a116ab053df05450a59181239200454/greenlet-3.5.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:76dae33e97b52743a19210931ee3e78a88fe1438bc2fc4ee5e7512d289bfad4f", size = 288351, upload-time = "2026-06-17T17:36:17.019Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/1b/bd98062fcef6d0e9d0873ab6f2d029772e6ea342972ae43275bd6177900f/greenlet-3.5.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30252d191d6959df1d040b559a38fc017139606c5ecc2ad00416557c0355d742", size = 604273, upload-time = "2026-06-17T18:07:20.296Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/e6/fe392c522bf45d976abe7db2793f6ef4e87b053ebb869deeaae46aeb54da/greenlet-3.5.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1adc23c50f22b0f5979521909a8360ab4a3d3bef8b641ce633a04cf1b1c967ea", size = 616536, upload-time = "2026-06-17T18:29:43.205Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/4a/399ff81fa93a19d6a9df394cef0355f082dbc19ad41aba9593cd0ad444e2/greenlet-3.5.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f052fff492c52fdfa99bd3b3c1389a53de37dae76a0562741417f0d018f02b3", size = 613749, upload-time = "2026-06-17T17:39:28.148Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a5/75/f519593f12ad43d08e28c03a95cfe2eeae011707dbc9dab0c4a263ce90f9/greenlet-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:120b77c2a18ebf629c3a7886f68c6d01e065654844ad468f15bb93ace66f2094", size = 1573725, upload-time = "2026-06-17T18:22:12.023Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/bc/bc1ea4b0754c6c51bbf9d94677b0b1f7fbda8cbb404e44a896854fc0a940/greenlet-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a850f6224088ef7dcc70f1a545cb6b3d119c35d6dca63b925b9f35da0635cdad", size = 1638132, upload-time = "2026-06-17T17:40:06.971Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/36/c0/f0f5a34247df60de285f75f22e57f14027f4b3c43820981854b5b643ca6d/greenlet-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:89da99ee8345b458ea2f16831dad31c88ddcdec454b48704d569a0b8fb28f146", size = 239393, upload-time = "2026-06-17T17:33:47.09Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/09/17/a8544e165445f30aea67a8d9cf2786d2bb0eb1b0e0d224b4d9bd80e2d587/greenlet-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:ca92411942154023c65851e6077d8ca0d00f19de5fa80bb2c6f196ff6c920ba9", size = 237723, upload-time = "2026-06-17T17:36:47.776Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/3c/bb37b9d40d65b0741a8b040ca5c307034d0a9822994dff5f825c88dd7a6b/greenlet-3.5.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0629377725977252159de1ebd3c6e49c170a63856e585446797bb3d66d4d9c34", size = 287178, upload-time = "2026-06-17T17:35:25.132Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f0/a6/0c5902393f492f8ceb19d0b5cf139284e3a11b333a049739643b1036b6f8/greenlet-3.5.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2ddf9eddc617681108dd071b3feabf3f4a4cd64846254aec4d4ceda098b639a", size = 606900, upload-time = "2026-06-17T18:07:21.692Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d8/7c/42899c31d4b87148ae4e3f87f63e13398824be6241f4dde42ded95768a34/greenlet-3.5.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f41feb9f2b59e2e61ac9bea4e344ddd9396bf3cacb2583f73a3595ed7df6f8e7", size = 619265, upload-time = "2026-06-17T18:29:44.837Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d3/52/4ff8c98d3cfe62b4515f8584ae14510a58f35c549cc5292b78d9b7a40b70/greenlet-3.5.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09201fa698768db245920b00fdc86ee3e73540f01ca6db162be9632642e1a473", size = 616187, upload-time = "2026-06-17T17:39:29.473Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/a6/269c8bf9aefc13361ce1088f0e392b154cb21005de7862e42b5d782b81fd/greenlet-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1759fa4f14c398508cf20dc8037de55cc23ae8bd14c185c2718257837195ca5", size = 1573778, upload-time = "2026-06-17T18:22:13.497Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/9b/391d015cbc6323e81b14c02cf825fdca7e0049c9bb489bf4ac72883118ba/greenlet-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9318cdeb9abdbfdd8bc8464ee4a06dffde2c7846e1def138365a6240ab2c9a5", size = 1638092, upload-time = "2026-06-17T17:40:08.163Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/53/5b4df711f4356c62e85d9f819d87966d526d1cfb32bae49a8f7d6fc36ea4/greenlet-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:2c3b3311af72b3d3b03cc0f1ffd11f072e834be5d0444105cf715fc44434e39c", size = 239352, upload-time = "2026-06-17T17:38:51.593Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bb/b6/18efc3a329ec035c3f344b8f2b60356451950ddf9b7b64ff00023778a1dd/greenlet-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:f9bbd6216c45a563c2a61e478e038b439d9f248bde44f775ea37d339da643af4", size = 237635, upload-time = "2026-06-17T17:35:36.632Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c7/89/aaafc8e14de4ac882e02ccb963225329b0e8578aba4365e71eb678e45722/greenlet-3.5.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1c31219badba285858ba8ed117f403dea7fafee6bade9a1991875aae530c3ceb", size = 287676, upload-time = "2026-06-17T17:33:31.514Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/fc/2308249206c12ac70de7b9a00970f84f07d10b3cd60e05d2fbcaa84124e8/greenlet-3.5.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f96ed6f4adc1066954ae95f45717657cb67468ef3b89e9a3632e14a625a8f39", size = 653552, upload-time = "2026-06-17T18:07:23.493Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7c/24/47730d1f8f1336b9b089237521ed7a26eee997065dcb4cab81cdca333abc/greenlet-3.5.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5795e883e915333c0d5648faaa691857fbc7180136883edc377f50f0d509c2a8", size = 665756, upload-time = "2026-06-17T18:29:46.616Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/69/d6c99db15dc0b5e892ac3cc7b942c8b21f4a9cc3bd9ea0bc3b0f339ffbd4/greenlet-3.5.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26aed8d9503ca78889141a9739d71b383efea5f472a7c522b5410f7eb2a1b163", size = 663228, upload-time = "2026-06-17T17:39:31.073Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/88/9e603f448e2bc107c883e95817b980fb9b45ba6aea0299b2e9978124bea2/greenlet-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dbebc038fcdda8f8f21cce985fd04e34e0f42007e7fc7ab7ad285caf77974b95", size = 1620723, upload-time = "2026-06-17T18:22:14.817Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/11/91/26da17e3777858c16fdb8d020a4c68f3a03cb92f238de8f5351d5d5186e9/greenlet-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a207023f1cf8695fd82580b8099c09c5809be18bc2282362cdfb965dd884a317", size = 1684227, upload-time = "2026-06-17T17:40:09.536Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2d/44/b3a11f7aa34cb38f1b7f3df8bcd9fcd09bac9d342c2a2c9b8686c804bcd2/greenlet-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:c674a1dd4fe41f6a93febe7ab366ceabf15080ea31a9307811c56dac5f435f73", size = 240257, upload-time = "2026-06-17T17:35:23.359Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/de/e3/3b62145fe917311732041a258adb218248add00542e3131c48bd047fbed5/greenlet-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3c417cd6c593bbbef6f7aa31a79f37d3db7d18832fc56b694a2150130bde784e", size = 239038, upload-time = "2026-06-17T17:37:56.792Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/ac/d3bad483e9f6cd1848604fdffa32cac25846dd6dfcec0e6f81c790185518/greenlet-3.5.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a96457a30384de52d9c5d2fd33abf6c1daae3db392cd556738f408b1a79a1cf0", size = 295668, upload-time = "2026-06-17T17:36:02.293Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/00/e9/3a7e557b895fd0469b00cd0b2bd498ba950e8bfdf6d7adeecf2c5e4130a6/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4af5d4961818ab651d09c1448a03b1ba2a1726a076266ebb62330bab9f3238c", size = 652820, upload-time = "2026-06-17T18:07:24.95Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/67/6225d5c5e4afc04be0fd161eec82e4b72017e8a100d222f25d7b42b0140d/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1789a6244ea1ba61fd4386c9a6a31873e9b0234762103364be98ef87dcb19f3", size = 658697, upload-time = "2026-06-17T18:29:48.365Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/99/6324b8ef916dcaddccb340b304c992ca3f947614ce0f2685d438187300b8/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3be00501fb4a8c37f6b4b3c4773808ceb26ea65c7ea64fd5735d0f330b3786de", size = 656436, upload-time = "2026-06-17T17:39:32.509Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a9/ee/f5bf9daac27c5e1b011965f64b5630a32b415daf7381b312943629e12c2a/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1d554cd96841a68d464d75a3736f8e87408a7b02b1930a75fa32feb408ad62f8", size = 1617193, upload-time = "2026-06-17T18:22:16.252Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8a/21/b05d5b12715bda92ce27c118d64971d21e9b8f3563ed959a7d271e2d4223/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3dff6cd3aac35f6cd3fc23460105acf576f5faf6c378de0bc088bf37c913864a", size = 1677512, upload-time = "2026-06-17T17:40:10.771Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/97/1b8f1314b868041b327dc1051603e8142b826480cb0ecb8a7b7632aee9c4/greenlet-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:36cfea2aa075d544617176b2e84450480f0797070ad8799a8c41ada2fe449d32", size = 243145, upload-time = "2026-06-17T17:34:37.502Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/36/07/1b5311775e04c718a118c504d7a3a312430e2a1bd1347226aff4774e4549/greenlet-3.5.2-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:a0314aa832c94633355dc6f3ee54f195159533355a323f26926fc63b98b2ccbb", size = 288315, upload-time = "2026-06-17T17:34:34.04Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/cc/6abcd2a486b58b9f77b7a93b690d59cb2c11a5906ed2ad4c63c7b9c1113d/greenlet-3.5.2-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24c59cb7db9d5c694cb8fd0c76eef8e456b2123afdfa7e4b8f2a67a0860d7682", size = 659130, upload-time = "2026-06-17T18:07:26.354Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f2/12/f4aaad6d3d383233f700ab322568a4f29f2c701a4861d85f4811d99689b2/greenlet-3.5.2-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7bb811753703739ad318112f16eccfaabdac050037b6d092debaa8b23566b4ce", size = 669724, upload-time = "2026-06-17T18:29:50.13Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/91/2a/a089811fc31c6bf8742f40a4e73470d6d401cef18e4314eb20dc399b377c/greenlet-3.5.2-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d78b5c1c178dad90447f1b8452262709d3eef4c98f825569e74c9d0b2260ac9", size = 668089, upload-time = "2026-06-17T17:39:33.808Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0f/1c/2f47c7d5fcfa98a62b705bf9a0505d86f4563c0d81cab1f7159ff1e743b7/greenlet-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0977af2df83136f81c1f76e76d4e2fe7d0dc56ea9c101a86af26a95190b9ca32", size = 1625684, upload-time = "2026-06-17T18:22:17.664Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/bf/661dd24624f70b7b32972d7693d0344ecde10278f647d7b828baf739899c/greenlet-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f9ed777c6891d8253e54468576f55e27f8fc1a662a664f946a191003574c0a74", size = 1688043, upload-time = "2026-06-17T17:40:12.403Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/49/d9bde1d15a21296b3b521fe083eb8aabd54ac05d15de9832918f3d639543/greenlet-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:c0ea4eb3de23f0bac1d75205e10ccfa9b418b17b01a2d7bf19e3b69dda08900a", size = 240531, upload-time = "2026-06-17T17:35:47.448Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7f/4d/86d7768bd53e9907de0333df215c2018cd01a593b3715cbd79aa82dd94b7/greenlet-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:7a7bfc200be40d04961d7e80e8337d726c0c1a50777e588123c3ed8ba731dcb9", size = 239579, upload-time = "2026-06-17T17:39:39.954Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/92/15/907be5e8900901039bae752fa9a31c03a3c1e064833f35a4e49449184581/greenlet-3.5.2-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:98a52d6a50d4deaba304331d83ee3e10ebbdc1517fcca40b2715d1de4534065c", size = 296697, upload-time = "2026-06-17T17:37:15.887Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/95/5c/08c57be575c3d6a3c023bbf22144a1c7dc6ed4d134527bb36ded4dbf04a8/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1587ff8b58fdf806993ed1490a06ac19c22d47b219c68b30954380029045d8d4", size = 656710, upload-time = "2026-06-17T18:07:28.046Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8c/d0/749f917bdc9fc90fceea4aa65fbf6556e617a50714d1496bdc8ad190bb36/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:feb721811d2754bfd16b48de151dd6b1f222c048e625151f2ca44cfdfd69f59c", size = 662629, upload-time = "2026-06-17T18:29:51.728Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/a5/68cefae3a07f6d0093a490cf28ab604f14578f3e60205a2a2b2d5cd70af2/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fe6062b1f35534e1e8fb28dfed406cf4eeff3e0bca3a0d9f8ff69f20a4abb00", size = 660147, upload-time = "2026-06-17T17:39:35.068Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/6b/b9156d8397e4750220f54c7c5c34650f1e740a8d2f66eab9cfd1b7b53b69/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b4ac902af825cbac8e9b2fccab8122236fd2ba6c8b71a080116d2c2ec72671b1", size = 1621675, upload-time = "2026-06-17T18:22:18.873Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b0/e3/d3250f4fa01c211a93d04e34fded63187e648dbec17b9b1a14d388040593/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6f1e473c06ae8be00c9034c2bb10fa277b08a93287e3111c395b839f01d27e1f", size = 1680577, upload-time = "2026-06-17T17:40:14.055Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/55/ba/eaee8bda4419770d7096b5a009ebff0ab20a2a28cdd83c4b591bfdf36fa9/greenlet-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:3c2315045f9983e2e50d7e89d95405c21bddb8745f2da4487bc080ab3525f904", size = 243482, upload-time = "2026-06-17T17:37:34.741Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/45/f794a81c91e9942c61f9110bd1f9a38a0ea565eab57f8b08cd53d3131e48/greenlet-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:db548d5ab6c2a8ead82c013f875090d79b5d7d2b67fc513934ce6cf66492ad7f", size = 242062, upload-time = "2026-06-17T17:35:39.814Z" },
]
[[package]]
name = "griffelib"
version = "2.1.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/33/e4/8d187ea29c2e30b3a09505c567513077d6117861bde1fbd997a167f262ec/griffelib-2.1.0.tar.gz", hash = "sha256:762a186d2c6fd6794d4ea20d428d597ffb857cb56b66421651cbba15bdd5e813", size = 216234, upload-time = "2026-06-19T12:05:42.278Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/33/e4/8d187ea29c2e30b3a09505c567513077d6117861bde1fbd997a167f262ec/griffelib-2.1.0.tar.gz", hash = "sha256:762a186d2c6fd6794d4ea20d428d597ffb857cb56b66421651cbba15bdd5e813", size = 216234, upload-time = "2026-06-19T12:05:42.278Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl", hash = "sha256:cc7b3d2d2865ad0b909fcc38086e3f554b5ea7acbaa7bbb7ecaa3f5dfb7d9f00", size = 142560, upload-time = "2026-06-19T12:05:38.742Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl", hash = "sha256:cc7b3d2d2865ad0b909fcc38086e3f554b5ea7acbaa7bbb7ecaa3f5dfb7d9f00", size = 142560, upload-time = "2026-06-19T12:05:38.742Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "httpcore"
version = "1.0.9"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "id"
version = "1.6.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" },
]
[[package]]
name = "identify"
version = "2.6.19"
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "idna"
version = "3.18"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
]
[[package]]
name = "importlib-metadata"
version = "8.7.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "zipp" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
]
[[package]]
name = "importlib-resources"
version = "6.5.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "ipdb"
version = "0.13.13"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "decorator" },
{ name = "ipython" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042, upload-time = "2023-03-09T15:40:57.487Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042, upload-time = "2023-03-09T15:40:57.487Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130, upload-time = "2023-03-09T15:40:55.021Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130, upload-time = "2023-03-09T15:40:55.021Z" },
]
[[package]]
name = "ipython"
version = "8.39.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "decorator" },
@@ -1236,234 +1259,234 @@ dependencies = [
{ name = "traitlets" },
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" },
]
[[package]]
name = "isoduration"
version = "20.11.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "arrow" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
]
[[package]]
name = "jaraco-classes"
version = "3.4.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "jedi"
version = "0.20.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "parso" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "markupsafe" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "joblib"
version = "1.5.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
]
[[package]]
name = "joserfc"
version = "1.7.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "cryptography" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/44/90/25cb27518750218e4f850be63d8bbb2343efaad1c01c3571aaa4b3c33bd7/joserfc-1.7.1.tar.gz", hash = "sha256:77d0b76514879c68c6f433bc5b7357a4ab72008ff1e33d8379fd11d72bd8ca81", size = 233181, upload-time = "2026-06-08T07:21:33.412Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/44/90/25cb27518750218e4f850be63d8bbb2343efaad1c01c3571aaa4b3c33bd7/joserfc-1.7.1.tar.gz", hash = "sha256:77d0b76514879c68c6f433bc5b7357a4ab72008ff1e33d8379fd11d72bd8ca81", size = 233181, upload-time = "2026-06-08T07:21:33.412Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b3/00/fa62404c3e347f946faa13aa21085205f9cc06ad17671e37f81a51662ae8/joserfc-1.7.1-py3-none-any.whl", hash = "sha256:b3e3d655612e2e1ef67b2600f2f420e12e537b020208fab1761fad647319c164", size = 70423, upload-time = "2026-06-08T07:21:32.001Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b3/00/fa62404c3e347f946faa13aa21085205f9cc06ad17671e37f81a51662ae8/joserfc-1.7.1-py3-none-any.whl", hash = "sha256:b3e3d655612e2e1ef67b2600f2f420e12e537b020208fab1761fad647319c164", size = 70423, upload-time = "2026-06-08T07:21:32.001Z" },
]
[[package]]
name = "jschema-to-python"
version = "1.2.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "attrs" },
{ name = "jsonpickle" },
{ name = "pbr" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1d/7f/5ae3d97ddd86ec33323231d68453afd504041efcfd4f4dde993196606849/jschema_to_python-1.2.3.tar.gz", hash = "sha256:76ff14fe5d304708ccad1284e4b11f96a658949a31ee7faed9e0995279549b91", size = 10061, upload-time = "2019-10-05T20:02:39.657Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/7f/5ae3d97ddd86ec33323231d68453afd504041efcfd4f4dde993196606849/jschema_to_python-1.2.3.tar.gz", hash = "sha256:76ff14fe5d304708ccad1284e4b11f96a658949a31ee7faed9e0995279549b91", size = 10061, upload-time = "2019-10-05T20:02:39.657Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/31/9e/1b6819a87c3f59170406163ba17bc55b0abe18ae552f53d2b0a2025f9c63/jschema_to_python-1.2.3-py3-none-any.whl", hash = "sha256:8a703ca7604d42d74b2815eecf99a33359a8dccbb80806cce386d5e2dd992b05", size = 10400, upload-time = "2019-10-05T20:02:37.948Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/31/9e/1b6819a87c3f59170406163ba17bc55b0abe18ae552f53d2b0a2025f9c63/jschema_to_python-1.2.3-py3-none-any.whl", hash = "sha256:8a703ca7604d42d74b2815eecf99a33359a8dccbb80806cce386d5e2dd992b05", size = 10400, upload-time = "2019-10-05T20:02:37.948Z" },
]
[[package]]
name = "jsonpatch"
version = "1.33"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jsonpointer" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" },
]
[[package]]
name = "jsonpath-ng"
version = "1.8.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/32/58/250751940d75c8019659e15482d548a4aa3b6ce122c515102a4bfdac50e3/jsonpath_ng-1.8.0.tar.gz", hash = "sha256:54252968134b5e549ea5b872f1df1168bd7defe1a52fed5a358c194e1943ddc3", size = 74513, upload-time = "2026-02-24T14:42:06.182Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/32/58/250751940d75c8019659e15482d548a4aa3b6ce122c515102a4bfdac50e3/jsonpath_ng-1.8.0.tar.gz", hash = "sha256:54252968134b5e549ea5b872f1df1168bd7defe1a52fed5a358c194e1943ddc3", size = 74513, upload-time = "2026-02-24T14:42:06.182Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/03/99/33c7d78a3fb70d545fd5411ac67a651c81602cc09c9cf0df383733f068c5/jsonpath_ng-1.8.0-py3-none-any.whl", hash = "sha256:b8dde192f8af58d646fc031fac9c99fe4d00326afc4148f1f043c601a8cfe138", size = 67844, upload-time = "2026-02-28T00:53:19.637Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/03/99/33c7d78a3fb70d545fd5411ac67a651c81602cc09c9cf0df383733f068c5/jsonpath_ng-1.8.0-py3-none-any.whl", hash = "sha256:b8dde192f8af58d646fc031fac9c99fe4d00326afc4148f1f043c601a8cfe138", size = 67844, upload-time = "2026-02-28T00:53:19.637Z" },
]
[[package]]
name = "jsonpickle"
version = "4.1.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8d/c0/dde9b4b42cc415b9579573f967f12efbb034e427a2a37e93ad5139891d87/jsonpickle-4.1.2.tar.gz", hash = "sha256:8afed18aa189fd81e2e833b426bb4af485594921f0b1d36c2001fc5637a2f210", size = 319120, upload-time = "2026-05-28T03:50:11.892Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/c0/dde9b4b42cc415b9579573f967f12efbb034e427a2a37e93ad5139891d87/jsonpickle-4.1.2.tar.gz", hash = "sha256:8afed18aa189fd81e2e833b426bb4af485594921f0b1d36c2001fc5637a2f210", size = 319120, upload-time = "2026-05-28T03:50:11.892Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a1/7b/fd3c7a09649aea9da1d3587aea624d8f9b29963dfd84a1bdb2aa93b36dac/jsonpickle-4.1.2-py3-none-any.whl", hash = "sha256:7ffe34426bc797684dbf1dc84185558bd864cd25b1ff5fb01b7405e392d0a937", size = 47203, upload-time = "2026-05-28T03:50:10.605Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a1/7b/fd3c7a09649aea9da1d3587aea624d8f9b29963dfd84a1bdb2aa93b36dac/jsonpickle-4.1.2-py3-none-any.whl", hash = "sha256:7ffe34426bc797684dbf1dc84185558bd864cd25b1ff5fb01b7405e392d0a937", size = 47203, upload-time = "2026-05-28T03:50:10.605Z" },
]
[[package]]
name = "jsonpointer"
version = "3.1.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" },
]
[[package]]
name = "jsonschema"
-version = "4.26.0"
-source = { registry = "https://pypi.org/simple" }
+version = "4.25.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
- { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "rpds-py", version = "0.30.0", source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }, marker = "python_full_version < '3.11'" },
+ { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }, marker = "python_full_version >= '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.optional-dependencies]
-format = [
+format-nongpl = [
{ name = "fqdn" },
{ name = "idna" },
{ name = "isoduration" },
{ name = "jsonpointer" },
{ name = "rfc3339-validator" },
- { name = "rfc3987" },
+ { name = "rfc3986-validator" },
+ { name = "rfc3987-syntax" },
{ name = "uri-template" },
{ name = "webcolors" },
]
[[package]]
name = "jsonschema-path"
-version = "0.5.0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.4.6"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
- { name = "attrs" },
{ name = "pathable" },
{ name = "pyyaml" },
{ name = "referencing" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/39/79/cd02a4df6d9270efdc7d3feefe6edd730b0820c39eeaa107a2faee8322d5/jsonschema_path-0.5.0.tar.gz", hash = "sha256:493b156ba895c97602655b620a8456caa2ce08c1aa389f5a7addec065e6e855c", size = 19597, upload-time = "2026-05-19T20:45:00.971Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/01/86/cfee6dd25843bec0760f456599a4f7e7e40221a934b9229fda0662c859bc/jsonschema_path-0.4.6.tar.gz", hash = "sha256:c89eb635f4d497c9ac328eeff359c489755838806a7d033510a692e9576f5c4b", size = 15302, upload-time = "2026-04-27T18:57:08.412Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/04/2c/9e69d73c4297508be9e3b64a970ea3971b3eb8db64ffc5802d40bd25981f/jsonschema_path-0.5.0-py3-none-any.whl", hash = "sha256:2790a070bc7abb08ea3dbe4d340ece4efadf639223001f020c7503229ba068e2", size = 24077, upload-time = "2026-05-19T20:44:59.225Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/43/3d3065c05a04bb550c143bfbb8e4fd7022cd327e1082bf257bac74923783/jsonschema_path-0.4.6-py3-none-any.whl", hash = "sha256:451354b5311fa955c3144e6e4e255388c751c0121c5570ec5bb9291dd42d08c9", size = 19565, upload-time = "2026-04-27T18:57:06.792Z" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "keyring"
version = "25.7.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "importlib-metadata", marker = "python_full_version < '3.12'" },
{ name = "jaraco-classes" },
@@ -1473,383 +1496,453 @@ dependencies = [
{ 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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "lazy-object-proxy"
version = "1.12.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d6/2b/d5e8915038acbd6c6a9fcb8aaf923dc184222405d3710285a1fec6e262bc/lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519", size = 26658, upload-time = "2025-08-22T13:42:23.373Z" },
- { url = "https://files.pythonhosted.org/packages/da/8f/91fc00eeea46ee88b9df67f7c5388e60993341d2a406243d620b2fdfde57/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6", size = 68412, upload-time = "2025-08-22T13:42:24.727Z" },
- { url = "https://files.pythonhosted.org/packages/07/d2/b7189a0e095caedfea4d42e6b6949d2685c354263bdf18e19b21ca9b3cd6/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b", size = 67559, upload-time = "2025-08-22T13:42:25.875Z" },
- { url = "https://files.pythonhosted.org/packages/a3/ad/b013840cc43971582ff1ceaf784d35d3a579650eb6cc348e5e6ed7e34d28/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8", size = 66651, upload-time = "2025-08-22T13:42:27.427Z" },
- { url = "https://files.pythonhosted.org/packages/7e/6f/b7368d301c15612fcc4cd00412b5d6ba55548bde09bdae71930e1a81f2ab/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8", size = 66901, upload-time = "2025-08-22T13:42:28.585Z" },
- { url = "https://files.pythonhosted.org/packages/61/1b/c6b1865445576b2fc5fa0fbcfce1c05fee77d8979fd1aa653dd0f179aefc/lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab", size = 26536, upload-time = "2025-08-22T13:42:29.636Z" },
- { url = "https://files.pythonhosted.org/packages/01/b3/4684b1e128a87821e485f5a901b179790e6b5bc02f89b7ee19c23be36ef3/lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff", size = 26656, upload-time = "2025-08-22T13:42:30.605Z" },
- { url = "https://files.pythonhosted.org/packages/3a/03/1bdc21d9a6df9ff72d70b2ff17d8609321bea4b0d3cffd2cea92fb2ef738/lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad", size = 68832, upload-time = "2025-08-22T13:42:31.675Z" },
- { url = "https://files.pythonhosted.org/packages/3d/4b/5788e5e8bd01d19af71e50077ab020bc5cce67e935066cd65e1215a09ff9/lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00", size = 69148, upload-time = "2025-08-22T13:42:32.876Z" },
- { url = "https://files.pythonhosted.org/packages/79/0e/090bf070f7a0de44c61659cb7f74c2fe02309a77ca8c4b43adfe0b695f66/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508", size = 67800, upload-time = "2025-08-22T13:42:34.054Z" },
- { url = "https://files.pythonhosted.org/packages/cf/d2/b320325adbb2d119156f7c506a5fbfa37fcab15c26d13cf789a90a6de04e/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa", size = 68085, upload-time = "2025-08-22T13:42:35.197Z" },
- { url = "https://files.pythonhosted.org/packages/6a/48/4b718c937004bf71cd82af3713874656bcb8d0cc78600bf33bb9619adc6c/lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370", size = 26535, upload-time = "2025-08-22T13:42:36.521Z" },
- { url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" },
- { url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" },
- { url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" },
- { url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" },
- { url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" },
- { url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" },
- { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" },
- { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" },
- { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" },
- { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" },
- { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" },
- { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" },
- { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" },
- { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" },
- { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" },
- { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" },
- { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" },
- { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" },
- { url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" },
- { url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" },
- { url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" },
- { url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" },
- { url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" },
- { url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" },
- { url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/2b/d5e8915038acbd6c6a9fcb8aaf923dc184222405d3710285a1fec6e262bc/lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519", size = 26658, upload-time = "2025-08-22T13:42:23.373Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/8f/91fc00eeea46ee88b9df67f7c5388e60993341d2a406243d620b2fdfde57/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6", size = 68412, upload-time = "2025-08-22T13:42:24.727Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/07/d2/b7189a0e095caedfea4d42e6b6949d2685c354263bdf18e19b21ca9b3cd6/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b", size = 67559, upload-time = "2025-08-22T13:42:25.875Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/ad/b013840cc43971582ff1ceaf784d35d3a579650eb6cc348e5e6ed7e34d28/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8", size = 66651, upload-time = "2025-08-22T13:42:27.427Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/6f/b7368d301c15612fcc4cd00412b5d6ba55548bde09bdae71930e1a81f2ab/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8", size = 66901, upload-time = "2025-08-22T13:42:28.585Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/61/1b/c6b1865445576b2fc5fa0fbcfce1c05fee77d8979fd1aa653dd0f179aefc/lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab", size = 26536, upload-time = "2025-08-22T13:42:29.636Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/01/b3/4684b1e128a87821e485f5a901b179790e6b5bc02f89b7ee19c23be36ef3/lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff", size = 26656, upload-time = "2025-08-22T13:42:30.605Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3a/03/1bdc21d9a6df9ff72d70b2ff17d8609321bea4b0d3cffd2cea92fb2ef738/lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad", size = 68832, upload-time = "2025-08-22T13:42:31.675Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/4b/5788e5e8bd01d19af71e50077ab020bc5cce67e935066cd65e1215a09ff9/lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00", size = 69148, upload-time = "2025-08-22T13:42:32.876Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/79/0e/090bf070f7a0de44c61659cb7f74c2fe02309a77ca8c4b43adfe0b695f66/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508", size = 67800, upload-time = "2025-08-22T13:42:34.054Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cf/d2/b320325adbb2d119156f7c506a5fbfa37fcab15c26d13cf789a90a6de04e/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa", size = 68085, upload-time = "2025-08-22T13:42:35.197Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/48/4b718c937004bf71cd82af3713874656bcb8d0cc78600bf33bb9619adc6c/lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370", size = 26535, upload-time = "2025-08-22T13:42:36.521Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" },
]
[[package]]
name = "license-expression"
version = "30.4.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "line-profiler"
version = "5.0.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/03/b6/6d18ad201417a9c5168995541d0fd7981b5652b2b34f6e46a3a93c0f1beb/line_profiler-5.0.2.tar.gz", hash = "sha256:8d8a990c84c64bcde45af22af502d17bc0ae107be405ce41bba92af5c39c0000", size = 407075, upload-time = "2026-02-23T23:31:20.698Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/22/9c/d2ba5e1f7da98e3dff9c333dd914c284cd733827987e7ed6a039c7fc008c/line_profiler-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ab5d8de6d3c0381b477cc73dde9c36b6c52ba1928d6daba85ac9e790a3f0086", size = 651703, upload-time = "2026-02-23T23:29:45.092Z" },
- { url = "https://files.pythonhosted.org/packages/1b/20/9f99d89ff0ad56e5e6190262ce16a8b2dad1f23b9dc0bc4da608fd42c16f/line_profiler-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45da5408286b5395ccb707d1cb2b5aeeb8828466cd2f62e8ab2d7cfb0e1b38c", size = 508846, upload-time = "2026-02-23T23:29:47.341Z" },
- { url = "https://files.pythonhosted.org/packages/5a/44/04d4f21dd1ffca9911402a8cc0ded6f9d89dfd5d3f2a0498704502e5b9af/line_profiler-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f6163a43474584db9da495d00869d51a66fdef3962ec3df76f998b1a89308123", size = 497428, upload-time = "2026-02-23T23:29:49.008Z" },
- { url = "https://files.pythonhosted.org/packages/bd/61/7e4658db06e3e3c41713bc600fc26e22607b08969d6044dd640ca26613b1/line_profiler-5.0.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7759e9e4688ed1be1e674dee599500ba47f2f2c76f903184df615352bc182a8", size = 1486964, upload-time = "2026-02-23T23:29:50.849Z" },
- { url = "https://files.pythonhosted.org/packages/03/2e/5507cf3190052906ba9fe77477cf655d446a9c517a41c290050e05cd1fec/line_profiler-5.0.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0f8b3b766bde49ca8c1e26b5ff9013358435106d11bc4838764e117d2bcd3ed", size = 1499663, upload-time = "2026-02-23T23:29:53.578Z" },
- { url = "https://files.pythonhosted.org/packages/c0/d9/cb19fb7b899f30d2261bb792d8f9d1e0b6ba5b4f3fc94b45136fd412562a/line_profiler-5.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:42d4bef2ce9f2e2cc6b872034ef4bf2e18ec44f3a8a09bdf91232a74abe4074c", size = 2442445, upload-time = "2026-02-23T23:29:55.952Z" },
- { url = "https://files.pythonhosted.org/packages/83/5d/9b1d142f41ae23b6da22954ec42c367491bcad356c3507f291c101522e88/line_profiler-5.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7dd5942d091541803b38ebc4c9d7f375d43b93e41e811f2449a022fd5b24d283", size = 2525207, upload-time = "2026-02-23T23:29:57.671Z" },
- { url = "https://files.pythonhosted.org/packages/68/a8/d9ac8429b74b1e30e22e2d51bc5d534864dd276ba856ea8ca32fca1f1198/line_profiler-5.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c22d1d8ee371e92149b5d9578e78072bcb36435adb62e84bf3bb0c173e14c6f6", size = 479862, upload-time = "2026-02-23T23:29:59.178Z" },
- { url = "https://files.pythonhosted.org/packages/d1/cd/a92661cb24987d0a4cf86f7ec9f6a0f74ea981c520b6458275d41b11ec0a/line_profiler-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:602370a9bb8d020ea28ddac3bb7fd4331c91d495e6e81d5f75752cbb2f2bb802", size = 650547, upload-time = "2026-02-23T23:30:00.593Z" },
- { url = "https://files.pythonhosted.org/packages/7e/77/5489458f8cc01ea00cdf25bc6fa74e748a30e7b758275cc98b485b59de91/line_profiler-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26956eef9668f641c9c6f9adb6b1b236252a73405e238452768812af14f9a145", size = 507989, upload-time = "2026-02-23T23:30:01.848Z" },
- { url = "https://files.pythonhosted.org/packages/cc/4f/14aace66e067fb5a774580bc71b348c323c91a4e2ac223a98822de67ecda/line_profiler-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64376a726009b7842706a3fef2a6dba051f0bf5a98cbbcafa4c23d6c83bac53c", size = 497137, upload-time = "2026-02-23T23:30:03.515Z" },
- { url = "https://files.pythonhosted.org/packages/52/48/ea92cc96538a192fdf74249f28ad9e9c0526743b12817f920985e7fdbbe2/line_profiler-5.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68432878d7598916f02be3fbb83e3a4727443cfd96af9cbea05cc1ae9749ed82", size = 1526617, upload-time = "2026-02-23T23:30:05.211Z" },
- { url = "https://files.pythonhosted.org/packages/2f/8d/c73544fba5683a50d7579f21614942d9223e2dd618986be56d5beff561e5/line_profiler-5.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7956e79526dc4898ca63dd8e3f435ff674b35d7e06457bfff883efae3ecb8359", size = 1540576, upload-time = "2026-02-23T23:30:07.234Z" },
- { url = "https://files.pythonhosted.org/packages/bf/33/a3255c143148e5886179b689b0413bb0b7edbd6af1531db577f8bf8b69fd/line_profiler-5.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29370b9d239c0e68ea017bbf2b582beed6122a439ef97a8e38228708b52ba595", size = 2480641, upload-time = "2026-02-23T23:30:08.638Z" },
- { url = "https://files.pythonhosted.org/packages/fb/c8/d398438f9af55c4bd799c15c6a9fc5d349c36ef461edce8ed3569768c964/line_profiler-5.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f47682cb1cef2a3b3865e59fdaf2f486196476fa508ddcdd838e3484626c2a68", size = 2565285, upload-time = "2026-02-23T23:30:10.015Z" },
- { url = "https://files.pythonhosted.org/packages/74/08/663f3dd52ebebcb98cddca9ca4f4b51fcd43ce2c8c6b676de28e2ad6f384/line_profiler-5.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:b74416342261d0434e2ae0ff074ec0ecf0ea4e66ec2db5a95dd8b0ec7f2b1a8b", size = 480440, upload-time = "2026-02-23T23:30:11.588Z" },
- { url = "https://files.pythonhosted.org/packages/5a/c4/12ac6f1c139780301d23b9f64ccb160366063124e91134fcccfb24d8b9b7/line_profiler-5.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f0d51ddb054d38a37607b335475c8be9fae4152b01873d1fc1d6b6a317b66398", size = 464965, upload-time = "2026-02-23T23:30:13.262Z" },
- { url = "https://files.pythonhosted.org/packages/99/92/fb766e6355118d2a681c18525d4c005c146ec44b064ccfd70f4529d8d260/line_profiler-5.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:256c1d5e84a93254dbe656d0486322190cc68f6b517544edef17a9f00167e680", size = 646920, upload-time = "2026-02-23T23:30:14.692Z" },
- { url = "https://files.pythonhosted.org/packages/7c/9d/3583c1cdc740206de9e4734bdcf377d649b89ea876bc36001d95b3dea67d/line_profiler-5.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:892f0cd9967b101ce7528be2d388616037c73cb27830effd7493fa021165c622", size = 505695, upload-time = "2026-02-23T23:30:16.375Z" },
- { url = "https://files.pythonhosted.org/packages/27/60/412476a1d09beac783d11d3bbf85fe6c1e3d50058e3c28967fee59c46649/line_profiler-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d2d02735843c14337dae3e80d95a732b4657ef759def75162ef97a1aa7466aac", size = 495859, upload-time = "2026-02-23T23:30:18.036Z" },
- { url = "https://files.pythonhosted.org/packages/37/75/65028bad08264fd8f9c3f0fd405c539ff552c2d1cf2a00965157ad148973/line_profiler-5.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d2e166a86dc9c78c349ee18b592b98ebfb9dae615f63fc77cce5f5f751a6ad0", size = 1464882, upload-time = "2026-02-23T23:30:19.273Z" },
- { url = "https://files.pythonhosted.org/packages/0d/6c/2d0286f67e6bb2b00ae23f9af6df18bfc6bb1ac5d803a8f46bd3eb22a8f1/line_profiler-5.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a870b68af1539d718d030f4c4726d35cff4b14ab605147e65222933c5c0e10e", size = 1484331, upload-time = "2026-02-23T23:30:20.571Z" },
- { url = "https://files.pythonhosted.org/packages/4e/a4/b01359733214a1a85c5f86f3953b07deb61b267efa0328e8d436a1ad80ea/line_profiler-5.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fe8cd787caa2a02ca7e138832fa4cab1f198377eaf6e5e8263e8b7506157c454", size = 2411802, upload-time = "2026-02-23T23:30:21.995Z" },
- { url = "https://files.pythonhosted.org/packages/d1/f4/1fa91206a6c50091cf614fdd5c9d349eb3a57d23f5eb8be8fffe7e0525b9/line_profiler-5.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:70ff915ade9e3ec38ff043ff093b590bbb3055e6fc8b311e0fe14cd78fb2a7f7", size = 2495790, upload-time = "2026-02-23T23:30:23.448Z" },
- { url = "https://files.pythonhosted.org/packages/87/18/d389c72dce6c8318c088a7c29ee8961a913c8a1c6469888b517e8f47ddaf/line_profiler-5.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:026779b9dfca0f367174f5d34bcccffce2755db40a4389f0d8a531a2e3ca7cfc", size = 478790, upload-time = "2026-02-23T23:30:24.848Z" },
- { url = "https://files.pythonhosted.org/packages/3f/54/d171600a4190c07215090a88846ef0093b5bf34a81f8059115592dbb1354/line_profiler-5.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:fe22b927f05a61a0149976bf0d22d8e56fa742ec89f3d72358db71a1f440c77b", size = 462269, upload-time = "2026-02-23T23:30:26.237Z" },
- { url = "https://files.pythonhosted.org/packages/a7/64/856b920e026fbd239df875ec05e63583f7bd7f250805215ab6e132da11d1/line_profiler-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:016effba91d34d15229d41984e921a27f66a7b634f1d7adf6c57c743f3d6a0eb", size = 642642, upload-time = "2026-02-23T23:30:27.63Z" },
- { url = "https://files.pythonhosted.org/packages/3b/08/0a56fab0a36818af6ffc8073700db2f402db5a62477b69d938c19871d631/line_profiler-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:506e800dd408a8aafadf39ff4e4a1375ae7794910d00098f191520a2f390cb99", size = 503787, upload-time = "2026-02-23T23:30:29.226Z" },
- { url = "https://files.pythonhosted.org/packages/ed/9a/0ab45cf92b2c13261b475c440e18bb18d9497cc2ad5dfaf38c231c72b02b/line_profiler-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e67f77bcb349a663cb22819f65621bcd2a39889524dd890d1d88f8736841b7b", size = 493631, upload-time = "2026-02-23T23:30:30.502Z" },
- { url = "https://files.pythonhosted.org/packages/fb/15/a5b603f0c7c795aa656a95e2a70d139dc499b5d153b6a3129bbba6b6f913/line_profiler-5.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6b9d08e85fd48d254ae253e76dc72598e94200ef7002eb1ae0bab4cc9c5e41a", size = 1464022, upload-time = "2026-02-23T23:30:31.793Z" },
- { url = "https://files.pythonhosted.org/packages/27/6f/0f399c72eecaf8f8c00e84238b5786afc34d0a4ef5ad10c63c712715ba86/line_profiler-5.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31290e06ac25cd87fee46ebe979541d4ec7c8d6f15c5cbe5874a932b1cee95bb", size = 1483425, upload-time = "2026-02-23T23:30:33.15Z" },
- { url = "https://files.pythonhosted.org/packages/65/18/f4c642a29719a84d17ea8b58cd6e60943573a28228c30c568565ed5512aa/line_profiler-5.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d7fbcc2dbd8534fc6f7d2b440076749b2235cdc525eb177fefafeaf7550373f", size = 2410276, upload-time = "2026-02-23T23:30:34.943Z" },
- { url = "https://files.pythonhosted.org/packages/90/33/701203686e7d27a545e3bbc8e81fffc7d091c42ed33564be4e72376ef45b/line_profiler-5.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55f04671f48afcd90858c18fbdb2509463c77d717ed5424664f096e902206b6b", size = 2495283, upload-time = "2026-02-23T23:30:36.616Z" },
- { url = "https://files.pythonhosted.org/packages/34/e1/59fe065f67ed1fb8f974a9e3434685af1fc1f6a154489f7ab0992eab1c73/line_profiler-5.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:d2262d4bbbcf72bd430fc5763073792a0f1cb20e64de0f7ecf6e8ae16627d876", size = 479287, upload-time = "2026-02-23T23:30:38.152Z" },
- { url = "https://files.pythonhosted.org/packages/e9/83/89f6ae52fa77960404ee88fc078ee680e504bf1ab8724ac01430cee0f5a5/line_profiler-5.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:abf755b020d91b639cbc563015eca381ca64e6bd27ee55ef9004a3a17b6d4dcf", size = 461960, upload-time = "2026-02-23T23:30:39.657Z" },
- { url = "https://files.pythonhosted.org/packages/c6/ae/43caf21edd10a7f5e138bdffcad01ade9a704462a923054402bbadbe5364/line_profiler-5.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a1cc30f3f7877fec826d0f40f400ee6c99239dc6a2f587b8d90d06a42d29c8a5", size = 648335, upload-time = "2026-02-23T23:30:41.042Z" },
- { url = "https://files.pythonhosted.org/packages/34/90/8a1fb985dc582d140fc92608dec3037a484c5f8ab99ae05c24031aa68000/line_profiler-5.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f90923e1cc4ff8eda1d18e525089fca7bfd6dfe8817ec530a913a2c7444ba0fd", size = 508823, upload-time = "2026-02-23T23:30:42.16Z" },
- { url = "https://files.pythonhosted.org/packages/a4/01/855c55e195ac0aadb8ca4e4c65311f945ed02a2491b436bc33cee318d841/line_profiler-5.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cc3d0ecccb14f014d05b32f687d22adcb98bf59fdcc721e7a4330f0372a56f92", size = 499868, upload-time = "2026-02-23T23:30:44.188Z" },
- { url = "https://files.pythonhosted.org/packages/fc/48/fe73d6192a37637534366306a7871ef0f7ff5973bd87da082e4bf5ec0764/line_profiler-5.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5341f36e532e7ed28e323f5502a29b397b66a6708c6427a77f965148a2e5ddec", size = 1460660, upload-time = "2026-02-23T23:30:45.601Z" },
- { url = "https://files.pythonhosted.org/packages/49/1c/e1236e0f3c7ec1e19e74d61ac15143a7826b5767296de87bcf3aa26548a1/line_profiler-5.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cce501f9d996b317b599c0ae99e3eb1bd447874ef8fef1da330b27f3a23eb50", size = 1475222, upload-time = "2026-02-23T23:30:47.014Z" },
- { url = "https://files.pythonhosted.org/packages/8e/ad/02302fd2a82949277036bc557ecebddb9bc6282b76a4da7660258fe82111/line_profiler-5.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b237d82fb792c3db7c80a8675d3c48993d4421b14d96ae602f7fe9ccf1f85903", size = 2413428, upload-time = "2026-02-23T23:30:48.828Z" },
- { url = "https://files.pythonhosted.org/packages/b8/c7/b3efe646c8b9fdc6fe26720860276c8a2bb745ffe30f5bcbc9726b975673/line_profiler-5.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:74febeca89128a37a32e6500c99665943c0d11e6043f46ce95596d7d1e1732a7", size = 2494741, upload-time = "2026-02-23T23:30:50.755Z" },
- { url = "https://files.pythonhosted.org/packages/0e/ad/ddadd39eb92900f063f27e8f6d748c03dc2638873f07ebf3cee75f29711f/line_profiler-5.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:d6ce98faff60d9552a30e233648a848682b5d664a7e09e9669163a8f01e28147", size = 485700, upload-time = "2026-02-23T23:30:52.373Z" },
- { url = "https://files.pythonhosted.org/packages/d0/45/a529f355eea8fb790fbdee0273d6c0049dba3232a36e82c30d849b00e996/line_profiler-5.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:8be7cc5f4ed9ad87352129d1a494cf5ba7f0fced0472201d83ac9fbfa20f798b", size = 469781, upload-time = "2026-02-23T23:30:53.747Z" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/03/b6/6d18ad201417a9c5168995541d0fd7981b5652b2b34f6e46a3a93c0f1beb/line_profiler-5.0.2.tar.gz", hash = "sha256:8d8a990c84c64bcde45af22af502d17bc0ae107be405ce41bba92af5c39c0000", size = 407075, upload-time = "2026-02-23T23:31:20.698Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/9c/d2ba5e1f7da98e3dff9c333dd914c284cd733827987e7ed6a039c7fc008c/line_profiler-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ab5d8de6d3c0381b477cc73dde9c36b6c52ba1928d6daba85ac9e790a3f0086", size = 651703, upload-time = "2026-02-23T23:29:45.092Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1b/20/9f99d89ff0ad56e5e6190262ce16a8b2dad1f23b9dc0bc4da608fd42c16f/line_profiler-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45da5408286b5395ccb707d1cb2b5aeeb8828466cd2f62e8ab2d7cfb0e1b38c", size = 508846, upload-time = "2026-02-23T23:29:47.341Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/44/04d4f21dd1ffca9911402a8cc0ded6f9d89dfd5d3f2a0498704502e5b9af/line_profiler-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f6163a43474584db9da495d00869d51a66fdef3962ec3df76f998b1a89308123", size = 497428, upload-time = "2026-02-23T23:29:49.008Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/61/7e4658db06e3e3c41713bc600fc26e22607b08969d6044dd640ca26613b1/line_profiler-5.0.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7759e9e4688ed1be1e674dee599500ba47f2f2c76f903184df615352bc182a8", size = 1486964, upload-time = "2026-02-23T23:29:50.849Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/03/2e/5507cf3190052906ba9fe77477cf655d446a9c517a41c290050e05cd1fec/line_profiler-5.0.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0f8b3b766bde49ca8c1e26b5ff9013358435106d11bc4838764e117d2bcd3ed", size = 1499663, upload-time = "2026-02-23T23:29:53.578Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c0/d9/cb19fb7b899f30d2261bb792d8f9d1e0b6ba5b4f3fc94b45136fd412562a/line_profiler-5.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:42d4bef2ce9f2e2cc6b872034ef4bf2e18ec44f3a8a09bdf91232a74abe4074c", size = 2442445, upload-time = "2026-02-23T23:29:55.952Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/83/5d/9b1d142f41ae23b6da22954ec42c367491bcad356c3507f291c101522e88/line_profiler-5.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7dd5942d091541803b38ebc4c9d7f375d43b93e41e811f2449a022fd5b24d283", size = 2525207, upload-time = "2026-02-23T23:29:57.671Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/a8/d9ac8429b74b1e30e22e2d51bc5d534864dd276ba856ea8ca32fca1f1198/line_profiler-5.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c22d1d8ee371e92149b5d9578e78072bcb36435adb62e84bf3bb0c173e14c6f6", size = 479862, upload-time = "2026-02-23T23:29:59.178Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/cd/a92661cb24987d0a4cf86f7ec9f6a0f74ea981c520b6458275d41b11ec0a/line_profiler-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:602370a9bb8d020ea28ddac3bb7fd4331c91d495e6e81d5f75752cbb2f2bb802", size = 650547, upload-time = "2026-02-23T23:30:00.593Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/77/5489458f8cc01ea00cdf25bc6fa74e748a30e7b758275cc98b485b59de91/line_profiler-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26956eef9668f641c9c6f9adb6b1b236252a73405e238452768812af14f9a145", size = 507989, upload-time = "2026-02-23T23:30:01.848Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cc/4f/14aace66e067fb5a774580bc71b348c323c91a4e2ac223a98822de67ecda/line_profiler-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64376a726009b7842706a3fef2a6dba051f0bf5a98cbbcafa4c23d6c83bac53c", size = 497137, upload-time = "2026-02-23T23:30:03.515Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/52/48/ea92cc96538a192fdf74249f28ad9e9c0526743b12817f920985e7fdbbe2/line_profiler-5.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68432878d7598916f02be3fbb83e3a4727443cfd96af9cbea05cc1ae9749ed82", size = 1526617, upload-time = "2026-02-23T23:30:05.211Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2f/8d/c73544fba5683a50d7579f21614942d9223e2dd618986be56d5beff561e5/line_profiler-5.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7956e79526dc4898ca63dd8e3f435ff674b35d7e06457bfff883efae3ecb8359", size = 1540576, upload-time = "2026-02-23T23:30:07.234Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bf/33/a3255c143148e5886179b689b0413bb0b7edbd6af1531db577f8bf8b69fd/line_profiler-5.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29370b9d239c0e68ea017bbf2b582beed6122a439ef97a8e38228708b52ba595", size = 2480641, upload-time = "2026-02-23T23:30:08.638Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/c8/d398438f9af55c4bd799c15c6a9fc5d349c36ef461edce8ed3569768c964/line_profiler-5.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f47682cb1cef2a3b3865e59fdaf2f486196476fa508ddcdd838e3484626c2a68", size = 2565285, upload-time = "2026-02-23T23:30:10.015Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/08/663f3dd52ebebcb98cddca9ca4f4b51fcd43ce2c8c6b676de28e2ad6f384/line_profiler-5.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:b74416342261d0434e2ae0ff074ec0ecf0ea4e66ec2db5a95dd8b0ec7f2b1a8b", size = 480440, upload-time = "2026-02-23T23:30:11.588Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/c4/12ac6f1c139780301d23b9f64ccb160366063124e91134fcccfb24d8b9b7/line_profiler-5.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f0d51ddb054d38a37607b335475c8be9fae4152b01873d1fc1d6b6a317b66398", size = 464965, upload-time = "2026-02-23T23:30:13.262Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/92/fb766e6355118d2a681c18525d4c005c146ec44b064ccfd70f4529d8d260/line_profiler-5.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:256c1d5e84a93254dbe656d0486322190cc68f6b517544edef17a9f00167e680", size = 646920, upload-time = "2026-02-23T23:30:14.692Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7c/9d/3583c1cdc740206de9e4734bdcf377d649b89ea876bc36001d95b3dea67d/line_profiler-5.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:892f0cd9967b101ce7528be2d388616037c73cb27830effd7493fa021165c622", size = 505695, upload-time = "2026-02-23T23:30:16.375Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/60/412476a1d09beac783d11d3bbf85fe6c1e3d50058e3c28967fee59c46649/line_profiler-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d2d02735843c14337dae3e80d95a732b4657ef759def75162ef97a1aa7466aac", size = 495859, upload-time = "2026-02-23T23:30:18.036Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/75/65028bad08264fd8f9c3f0fd405c539ff552c2d1cf2a00965157ad148973/line_profiler-5.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d2e166a86dc9c78c349ee18b592b98ebfb9dae615f63fc77cce5f5f751a6ad0", size = 1464882, upload-time = "2026-02-23T23:30:19.273Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0d/6c/2d0286f67e6bb2b00ae23f9af6df18bfc6bb1ac5d803a8f46bd3eb22a8f1/line_profiler-5.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a870b68af1539d718d030f4c4726d35cff4b14ab605147e65222933c5c0e10e", size = 1484331, upload-time = "2026-02-23T23:30:20.571Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4e/a4/b01359733214a1a85c5f86f3953b07deb61b267efa0328e8d436a1ad80ea/line_profiler-5.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fe8cd787caa2a02ca7e138832fa4cab1f198377eaf6e5e8263e8b7506157c454", size = 2411802, upload-time = "2026-02-23T23:30:21.995Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/f4/1fa91206a6c50091cf614fdd5c9d349eb3a57d23f5eb8be8fffe7e0525b9/line_profiler-5.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:70ff915ade9e3ec38ff043ff093b590bbb3055e6fc8b311e0fe14cd78fb2a7f7", size = 2495790, upload-time = "2026-02-23T23:30:23.448Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/87/18/d389c72dce6c8318c088a7c29ee8961a913c8a1c6469888b517e8f47ddaf/line_profiler-5.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:026779b9dfca0f367174f5d34bcccffce2755db40a4389f0d8a531a2e3ca7cfc", size = 478790, upload-time = "2026-02-23T23:30:24.848Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3f/54/d171600a4190c07215090a88846ef0093b5bf34a81f8059115592dbb1354/line_profiler-5.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:fe22b927f05a61a0149976bf0d22d8e56fa742ec89f3d72358db71a1f440c77b", size = 462269, upload-time = "2026-02-23T23:30:26.237Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a7/64/856b920e026fbd239df875ec05e63583f7bd7f250805215ab6e132da11d1/line_profiler-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:016effba91d34d15229d41984e921a27f66a7b634f1d7adf6c57c743f3d6a0eb", size = 642642, upload-time = "2026-02-23T23:30:27.63Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3b/08/0a56fab0a36818af6ffc8073700db2f402db5a62477b69d938c19871d631/line_profiler-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:506e800dd408a8aafadf39ff4e4a1375ae7794910d00098f191520a2f390cb99", size = 503787, upload-time = "2026-02-23T23:30:29.226Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/9a/0ab45cf92b2c13261b475c440e18bb18d9497cc2ad5dfaf38c231c72b02b/line_profiler-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e67f77bcb349a663cb22819f65621bcd2a39889524dd890d1d88f8736841b7b", size = 493631, upload-time = "2026-02-23T23:30:30.502Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/15/a5b603f0c7c795aa656a95e2a70d139dc499b5d153b6a3129bbba6b6f913/line_profiler-5.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6b9d08e85fd48d254ae253e76dc72598e94200ef7002eb1ae0bab4cc9c5e41a", size = 1464022, upload-time = "2026-02-23T23:30:31.793Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/6f/0f399c72eecaf8f8c00e84238b5786afc34d0a4ef5ad10c63c712715ba86/line_profiler-5.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31290e06ac25cd87fee46ebe979541d4ec7c8d6f15c5cbe5874a932b1cee95bb", size = 1483425, upload-time = "2026-02-23T23:30:33.15Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/65/18/f4c642a29719a84d17ea8b58cd6e60943573a28228c30c568565ed5512aa/line_profiler-5.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d7fbcc2dbd8534fc6f7d2b440076749b2235cdc525eb177fefafeaf7550373f", size = 2410276, upload-time = "2026-02-23T23:30:34.943Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/90/33/701203686e7d27a545e3bbc8e81fffc7d091c42ed33564be4e72376ef45b/line_profiler-5.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55f04671f48afcd90858c18fbdb2509463c77d717ed5424664f096e902206b6b", size = 2495283, upload-time = "2026-02-23T23:30:36.616Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/34/e1/59fe065f67ed1fb8f974a9e3434685af1fc1f6a154489f7ab0992eab1c73/line_profiler-5.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:d2262d4bbbcf72bd430fc5763073792a0f1cb20e64de0f7ecf6e8ae16627d876", size = 479287, upload-time = "2026-02-23T23:30:38.152Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e9/83/89f6ae52fa77960404ee88fc078ee680e504bf1ab8724ac01430cee0f5a5/line_profiler-5.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:abf755b020d91b639cbc563015eca381ca64e6bd27ee55ef9004a3a17b6d4dcf", size = 461960, upload-time = "2026-02-23T23:30:39.657Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/ae/43caf21edd10a7f5e138bdffcad01ade9a704462a923054402bbadbe5364/line_profiler-5.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a1cc30f3f7877fec826d0f40f400ee6c99239dc6a2f587b8d90d06a42d29c8a5", size = 648335, upload-time = "2026-02-23T23:30:41.042Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/34/90/8a1fb985dc582d140fc92608dec3037a484c5f8ab99ae05c24031aa68000/line_profiler-5.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f90923e1cc4ff8eda1d18e525089fca7bfd6dfe8817ec530a913a2c7444ba0fd", size = 508823, upload-time = "2026-02-23T23:30:42.16Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a4/01/855c55e195ac0aadb8ca4e4c65311f945ed02a2491b436bc33cee318d841/line_profiler-5.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cc3d0ecccb14f014d05b32f687d22adcb98bf59fdcc721e7a4330f0372a56f92", size = 499868, upload-time = "2026-02-23T23:30:44.188Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fc/48/fe73d6192a37637534366306a7871ef0f7ff5973bd87da082e4bf5ec0764/line_profiler-5.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5341f36e532e7ed28e323f5502a29b397b66a6708c6427a77f965148a2e5ddec", size = 1460660, upload-time = "2026-02-23T23:30:45.601Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/1c/e1236e0f3c7ec1e19e74d61ac15143a7826b5767296de87bcf3aa26548a1/line_profiler-5.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cce501f9d996b317b599c0ae99e3eb1bd447874ef8fef1da330b27f3a23eb50", size = 1475222, upload-time = "2026-02-23T23:30:47.014Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/ad/02302fd2a82949277036bc557ecebddb9bc6282b76a4da7660258fe82111/line_profiler-5.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b237d82fb792c3db7c80a8675d3c48993d4421b14d96ae602f7fe9ccf1f85903", size = 2413428, upload-time = "2026-02-23T23:30:48.828Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/c7/b3efe646c8b9fdc6fe26720860276c8a2bb745ffe30f5bcbc9726b975673/line_profiler-5.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:74febeca89128a37a32e6500c99665943c0d11e6043f46ce95596d7d1e1732a7", size = 2494741, upload-time = "2026-02-23T23:30:50.755Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0e/ad/ddadd39eb92900f063f27e8f6d748c03dc2638873f07ebf3cee75f29711f/line_profiler-5.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:d6ce98faff60d9552a30e233648a848682b5d664a7e09e9669163a8f01e28147", size = 485700, upload-time = "2026-02-23T23:30:52.373Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/45/a529f355eea8fb790fbdee0273d6c0049dba3232a36e82c30d849b00e996/line_profiler-5.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:8be7cc5f4ed9ad87352129d1a494cf5ba7f0fced0472201d83ac9fbfa20f798b", size = 469781, upload-time = "2026-02-23T23:30:53.747Z" },
]
[[package]]
name = "lxml"
-version = "5.4.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/f5/1f/a3b6b74a451ceb84b471caa75c934d2430a4d84395d38ef201d539f38cd1/lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c", size = 8076838, upload-time = "2025-04-23T01:44:29.325Z" },
- { url = "https://files.pythonhosted.org/packages/36/af/a567a55b3e47135b4d1f05a1118c24529104c003f95851374b3748139dc1/lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7", size = 4381827, upload-time = "2025-04-23T01:44:33.345Z" },
- { url = "https://files.pythonhosted.org/packages/50/ba/4ee47d24c675932b3eb5b6de77d0f623c2db6dc466e7a1f199792c5e3e3a/lxml-5.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf", size = 5204098, upload-time = "2025-04-23T01:44:35.809Z" },
- { url = "https://files.pythonhosted.org/packages/f2/0f/b4db6dfebfefe3abafe360f42a3d471881687fd449a0b86b70f1f2683438/lxml-5.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28", size = 4930261, upload-time = "2025-04-23T01:44:38.271Z" },
- { url = "https://files.pythonhosted.org/packages/0b/1f/0bb1bae1ce056910f8db81c6aba80fec0e46c98d77c0f59298c70cd362a3/lxml-5.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609", size = 5529621, upload-time = "2025-04-23T01:44:40.921Z" },
- { url = "https://files.pythonhosted.org/packages/21/f5/e7b66a533fc4a1e7fa63dd22a1ab2ec4d10319b909211181e1ab3e539295/lxml-5.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4", size = 4983231, upload-time = "2025-04-23T01:44:43.871Z" },
- { url = "https://files.pythonhosted.org/packages/11/39/a38244b669c2d95a6a101a84d3c85ba921fea827e9e5483e93168bf1ccb2/lxml-5.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7", size = 5084279, upload-time = "2025-04-23T01:44:46.632Z" },
- { url = "https://files.pythonhosted.org/packages/db/64/48cac242347a09a07740d6cee7b7fd4663d5c1abd65f2e3c60420e231b27/lxml-5.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f", size = 4927405, upload-time = "2025-04-23T01:44:49.843Z" },
- { url = "https://files.pythonhosted.org/packages/98/89/97442835fbb01d80b72374f9594fe44f01817d203fa056e9906128a5d896/lxml-5.4.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997", size = 5550169, upload-time = "2025-04-23T01:44:52.791Z" },
- { url = "https://files.pythonhosted.org/packages/f1/97/164ca398ee654eb21f29c6b582685c6c6b9d62d5213abc9b8380278e9c0a/lxml-5.4.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c", size = 5062691, upload-time = "2025-04-23T01:44:56.108Z" },
- { url = "https://files.pythonhosted.org/packages/d0/bc/712b96823d7feb53482d2e4f59c090fb18ec7b0d0b476f353b3085893cda/lxml-5.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b", size = 5133503, upload-time = "2025-04-23T01:44:59.222Z" },
- { url = "https://files.pythonhosted.org/packages/d4/55/a62a39e8f9da2a8b6002603475e3c57c870cd9c95fd4b94d4d9ac9036055/lxml-5.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b", size = 4999346, upload-time = "2025-04-23T01:45:02.088Z" },
- { url = "https://files.pythonhosted.org/packages/ea/47/a393728ae001b92bb1a9e095e570bf71ec7f7fbae7688a4792222e56e5b9/lxml-5.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563", size = 5627139, upload-time = "2025-04-23T01:45:04.582Z" },
- { url = "https://files.pythonhosted.org/packages/5e/5f/9dcaaad037c3e642a7ea64b479aa082968de46dd67a8293c541742b6c9db/lxml-5.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5", size = 5465609, upload-time = "2025-04-23T01:45:07.649Z" },
- { url = "https://files.pythonhosted.org/packages/a7/0a/ebcae89edf27e61c45023005171d0ba95cb414ee41c045ae4caf1b8487fd/lxml-5.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776", size = 5192285, upload-time = "2025-04-23T01:45:10.456Z" },
- { url = "https://files.pythonhosted.org/packages/42/ad/cc8140ca99add7d85c92db8b2354638ed6d5cc0e917b21d36039cb15a238/lxml-5.4.0-cp310-cp310-win32.whl", hash = "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7", size = 3477507, upload-time = "2025-04-23T01:45:12.474Z" },
- { url = "https://files.pythonhosted.org/packages/e9/39/597ce090da1097d2aabd2f9ef42187a6c9c8546d67c419ce61b88b336c85/lxml-5.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250", size = 3805104, upload-time = "2025-04-23T01:45:15.104Z" },
- { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" },
- { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" },
- { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" },
- { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" },
- { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" },
- { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" },
- { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" },
- { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" },
- { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" },
- { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" },
- { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" },
- { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" },
- { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" },
- { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" },
- { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" },
- { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" },
- { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" },
- { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" },
- { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" },
- { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" },
- { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" },
- { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" },
- { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" },
- { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" },
- { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" },
- { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" },
- { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" },
- { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" },
- { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" },
- { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" },
- { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" },
- { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" },
- { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" },
- { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" },
- { url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
- { url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
- { url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
- { url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
- { url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
- { url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
- { url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
- { url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
- { url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
- { url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
- { url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
- { url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
- { url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
- { url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
- { url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
- { url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
- { url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
- { url = "https://files.pythonhosted.org/packages/c6/b0/e4d1cbb8c078bc4ae44de9c6a79fec4e2b4151b1b4d50af71d799e76b177/lxml-5.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55", size = 3892319, upload-time = "2025-04-23T01:49:22.069Z" },
- { url = "https://files.pythonhosted.org/packages/5b/aa/e2bdefba40d815059bcb60b371a36fbfcce970a935370e1b367ba1cc8f74/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740", size = 4211614, upload-time = "2025-04-23T01:49:24.599Z" },
- { url = "https://files.pythonhosted.org/packages/3c/5f/91ff89d1e092e7cfdd8453a939436ac116db0a665e7f4be0cd8e65c7dc5a/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5", size = 4306273, upload-time = "2025-04-23T01:49:27.355Z" },
- { url = "https://files.pythonhosted.org/packages/be/7c/8c3f15df2ca534589717bfd19d1e3482167801caedfa4d90a575facf68a6/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37", size = 4208552, upload-time = "2025-04-23T01:49:29.949Z" },
- { url = "https://files.pythonhosted.org/packages/7d/d8/9567afb1665f64d73fc54eb904e418d1138d7f011ed00647121b4dd60b38/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571", size = 4331091, upload-time = "2025-04-23T01:49:32.842Z" },
- { url = "https://files.pythonhosted.org/packages/f1/ab/fdbbd91d8d82bf1a723ba88ec3e3d76c022b53c391b0c13cad441cdb8f9e/lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", size = 3487862, upload-time = "2025-04-23T01:49:36.296Z" },
+version = "6.1.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/12/da/dbe4dfc01ac226fb0504fad035f4d69f3202f3502e20e68537631daddd96/lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60", size = 8541124, upload-time = "2026-05-18T19:17:11.589Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/20/f7095ed9fc2c025f9cfe71cc6ec9f1feb05624edc1812423b5f1aecf3d4b/lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d", size = 4602783, upload-time = "2026-05-18T19:17:20.888Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4a/a4/65c63ca98bd129f6cff7b8c2fa48953ab058cc6005b541354e7dd54d8000/lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea", size = 5002687, upload-time = "2026-05-18T19:17:01.738Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/96/1d/ab7a5c4b5a394d98a94e2d0fc67bab8297597426770dd4978370fbdaf531/lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074", size = 5155099, upload-time = "2026-05-18T19:17:05.159Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/b1/07603bfeeb891a2596d5c2a68f7d2f70f7d11c841ebe391412c69c2857b0/lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30", size = 5057225, upload-time = "2026-05-18T19:17:08.117Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7a/16/cb391ee4b90186fa16d9ebcbe3ea96c71b8da3b0686386c8dcbcc3c67d44/lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315", size = 5287643, upload-time = "2026-05-18T19:17:11.507Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/eb/d6/b619717f918fd76747448fdbaee0e769edbc70e659b5b5d0112b7020b7a3/lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1", size = 5412445, upload-time = "2026-05-18T19:17:22.182Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/80/12bc5390ac0a3edeb579d9535e5049a5dda663438728e179d52fb319c33a/lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206", size = 4770864, upload-time = "2026-05-18T19:17:26.851Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0b/59/6500c09da3137f54f020e908d81cfc5ee3e8888e908fd380207afad7c2e6/lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067", size = 5359594, upload-time = "2026-05-18T19:17:32.527Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f2/9b/f64b4cc6b7ebcf75d95af3cde934d254b5f2f10d4163928d838d86b6eb48/lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a", size = 5107713, upload-time = "2026-05-18T19:17:04.402Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/19/c7388ad5d3a72315d2832dc1458cbf4f2af7f2b990b606ff4876efd04511/lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa", size = 4803973, upload-time = "2026-05-18T19:17:06.545Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3f/22/76197f0bbf165f0b9e75be59be4997e5259cde973f12f098c1b54c7f5d60/lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383", size = 5349925, upload-time = "2026-05-18T19:17:09.743Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/52/d2a0cfeccb9bcdc47c7ee05cdae5d69b48c9acf20997790a6338bb0d0b3b/lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1", size = 5309825, upload-time = "2026-05-18T19:17:13.831Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/4a/b30944266776c2f49749ef2445aa7e78898194134b80ad776386f61b56ae/lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a", size = 3598402, upload-time = "2026-05-18T19:17:08.21Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/97/33691c66a4d7ec1a5a98e7c909a5b83ee45c7f7ba4cf92b1c4cf26e98079/lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5", size = 4021295, upload-time = "2026-05-18T19:17:28.638Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/5f/26a4dd0e12b9456ff7b12a21af5b491eb6629680d1edd73f4140fd386bcf/lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485", size = 3667717, upload-time = "2026-05-19T19:22:44.474Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ad/00/84c4b5302d42a2d0184f38d538c8a197f33b52a50bd4f7bcfe990bce3036/lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", size = 3594072, upload-time = "2026-05-18T19:17:12.714Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/61/9d/2e2f7d876349f45e0f3e29f72da311668853d59b58d473a2dea4f0160135/lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", size = 4025469, upload-time = "2026-05-18T19:17:50.566Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b0/d5/570e6390e4110331e6208b2ba83d1482cc9146808ee118b22824a34c1070/lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", size = 3667640, upload-time = "2026-05-19T19:22:48.293Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6b/55/a0c72851dfee5ecc689f949723a73dea457758912542cb955b108eaf0d8f/lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", size = 5082329, upload-time = "2026-05-18T19:19:09.728Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f0/b6/0608f7d61a3b96cc67e5648a3d906e31a5082093e10e7be65b3886289938/lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", size = 4993564, upload-time = "2026-05-18T19:19:13.608Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4c/66/ae227524b066d29d55bf0b453d93d2d793c40218657d643dcbbca13b8faf/lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", size = 5613467, upload-time = "2026-05-18T19:19:16.228Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a6/76/dbe4a00b50385e40194231dcfe5a12c059de7cf90e89c83407d2b085b719/lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", size = 5228304, upload-time = "2026-05-18T19:19:19.354Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/01/00b1b8442ed2041793336868ba0b9ea4b13d7da7c085c6404c207a63bf79/lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", size = 5341607, upload-time = "2026-05-18T19:19:22.297Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/36/1ad29931e9a4638bb707869f01d423a6c815f82152138d1a40dfcfde2b95/lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", size = 4700168, upload-time = "2026-05-18T19:19:25.133Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/d1/a9536cecf9be18a0dc72d32bead283a2332d1ffebd2dd3ac70ce444686e5/lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", size = 5232487, upload-time = "2026-05-18T19:19:28.603Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0e/77/b4fb1e03bf5d130e879214d3100092e386418807fb74dd0adc4b0a48f351/lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", size = 5044231, upload-time = "2026-05-18T19:18:42.246Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/4c/d00daeeb0a5530c4028a9232aa1b93db3ef4ed2158c116ea73c79a9765b3/lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", size = 4769450, upload-time = "2026-05-18T19:18:48.013Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/6a/715a3a8d156ce42f29cf014706f5410c2ff3b02267774110fc23266409fe/lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", size = 5635874, upload-time = "2026-05-18T19:18:51.914Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/45/37/0544bc21dde2a88f3a17b504e6fc79c0e01d25a33c2f6079724e9e72b9c7/lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", size = 5223987, upload-time = "2026-05-18T19:18:59.715Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/db/a4/441aee36c6f6b249823d20fd91f9be9ab89d7c5a8ae542a4a4ca6d342d56/lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", size = 3508989, upload-time = "2026-05-18T19:18:38.158Z" },
]
[[package]]
name = "mando"
version = "0.7.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "six" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/35/24/cd70d5ae6d35962be752feccb7dca80b5e0c2d450e995b16abd6275f3296/mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500", size = 37868, upload-time = "2022-02-24T08:12:27.316Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/24/cd70d5ae6d35962be752feccb7dca80b5e0c2d450e995b16abd6275f3296/mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500", size = 37868, upload-time = "2022-02-24T08:12:27.316Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a", size = 28149, upload-time = "2022-02-24T08:12:25.24Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a", size = 28149, upload-time = "2022-02-24T08:12:25.24Z" },
]
[[package]]
name = "markdown"
version = "3.10.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.2.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "mdurl" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
- { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
- { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
- { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
- { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
- { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
- { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
- { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
- { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
- { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
- { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
- { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
- { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
- { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
- { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
- { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
- { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
- { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
- { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
- { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
- { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
- { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
- { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
- { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
- { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
- { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
- { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
- { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
- { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
- { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
- { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
- { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
- { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
- { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
- { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
- { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
- { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
- { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
- { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
- { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
- { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
- { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
- { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
- { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
- { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
- { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
- { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
- { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
- { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
- { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
- { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
- { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
- { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
- { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
- { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
- { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
- { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
- { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
- { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
- { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
- { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
- { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
- { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
- { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
- { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
- { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
- { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
- { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
- { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
- { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
- { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
- { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
- { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
- { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
- { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
- { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
- { 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" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "marshmallow"
version = "4.3.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "backports-datetime-fromisoformat", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/25/7e/1dbd4096eb7c148cd2841841916f78820bb85a4d80a0c25c02d30815a7fb/marshmallow-4.3.0.tar.gz", hash = "sha256:fb43c53b3fe240b8f6af37223d6ef1636f927ad9bea8ab323afad95dff090880", size = 224485, upload-time = "2026-04-03T21:46:32.72Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/7e/1dbd4096eb7c148cd2841841916f78820bb85a4d80a0c25c02d30815a7fb/marshmallow-4.3.0.tar.gz", hash = "sha256:fb43c53b3fe240b8f6af37223d6ef1636f927ad9bea8ab323afad95dff090880", size = 224485, upload-time = "2026-04-03T21:46:32.72Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/e0/ff24e25218bb59eb6290a530cea40651b14068b6e3659b20f9c175179632/marshmallow-4.3.0-py3-none-any.whl", hash = "sha256:46c4fe6984707e3cbd485dfebbf0a59874f58d695aad05c1668d15e8c6e13b46", size = 49148, upload-time = "2026-04-03T21:46:31.241Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/e0/ff24e25218bb59eb6290a530cea40651b14068b6e3659b20f9c175179632/marshmallow-4.3.0-py3-none-any.whl", hash = "sha256:46c4fe6984707e3cbd485dfebbf0a59874f58d695aad05c1668d15e8c6e13b46", size = 49148, upload-time = "2026-04-03T21:46:31.241Z" },
]
[[package]]
name = "matplotlib-inline"
version = "0.2.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "traitlets" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" },
+]
+
+[[package]]
+name = "mcp"
+version = "1.23.3"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "mdurl"
version = "0.1.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "memory-profiler"
version = "0.61.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "psutil" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b2/88/e1907e1ca3488f2d9507ca8b0ae1add7b1cd5d3ca2bc8e5b329382ea2c7b/memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0", size = 35935, upload-time = "2022-11-15T17:57:28.994Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b2/88/e1907e1ca3488f2d9507ca8b0ae1add7b1cd5d3ca2bc8e5b329382ea2c7b/memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0", size = 35935, upload-time = "2022-11-15T17:57:28.994Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/49/26/aaca612a0634ceede20682e692a6c55e35a94c21ba36b807cc40fe910ae1/memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84", size = 31803, upload-time = "2022-11-15T17:57:27.031Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/26/aaca612a0634ceede20682e692a6c55e35a94c21ba36b807cc40fe910ae1/memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84", size = 31803, upload-time = "2022-11-15T17:57:27.031Z" },
]
[[package]]
name = "mergedeep"
version = "1.3.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jinja2" },
{ name = "mkdocs" },
@@ -1858,15 +1951,15 @@ dependencies = [
{ 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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "click" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -1882,69 +1975,69 @@ dependencies = [
{ name = "pyyaml-env-tag" },
{ name = "watchdog" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "mkdocs" },
{ name = "properdocs" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/43/43/428f312149c161cae557eecd35f3c4a82b867998b1d47fb29fdfe927be26/mkdocs_gen_files-0.6.1.tar.gz", hash = "sha256:57d7ff2229e23d077e46d14a33db6d37c8823f6ce1a503c874c1764a71679763", size = 8746, upload-time = "2026-03-16T23:26:09.31Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/43/43/428f312149c161cae557eecd35f3c4a82b867998b1d47fb29fdfe927be26/mkdocs_gen_files-0.6.1.tar.gz", hash = "sha256:57d7ff2229e23d077e46d14a33db6d37c8823f6ce1a503c874c1764a71679763", size = 8746, upload-time = "2026-03-16T23:26:09.31Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ee/1b/3075eb67fe66e19db059f0a25744c4e56978a309603a20e1d3353d545b5e/mkdocs_gen_files-0.6.1-py3-none-any.whl", hash = "sha256:b3182bfc6219e35b8d26658cb988368659d5d023aac30c2a819247558fc12189", size = 8282, upload-time = "2026-03-16T23:26:08.292Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ee/1b/3075eb67fe66e19db059f0a25744c4e56978a309603a20e1d3353d545b5e/mkdocs_gen_files-0.6.1-py3-none-any.whl", hash = "sha256:b3182bfc6219e35b8d26658cb988368659d5d023aac30c2a819247558fc12189", size = 8282, upload-time = "2026-03-16T23:26:08.292Z" },
]
[[package]]
name = "mkdocs-get-deps"
version = "0.2.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "mergedeep" },
{ name = "platformdirs" },
{ name = "pyyaml" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "mkdocs-literate-nav"
version = "0.6.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "mkdocs" },
{ name = "properdocs" },
]
-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://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "mkdocs-material"
version = "9.7.6"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "babel" },
{ name = "backrefs" },
@@ -1958,37 +2051,37 @@ dependencies = [
{ name = "pymdown-extensions" },
{ name = "requests" },
]
-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://pkgs.safetycli.com/package/fgo-inc/pypi/packages/45/29/6d2bcf41ae40802c4beda2432396fff97b8456fb496371d1bc7aad6512ec/mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69", size = 4097959, upload-time = "2026-03-19T15:41:58.161Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "mkdocs-material-extensions"
version = "1.3.1"
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "mkdocs-section-index"
version = "0.3.12"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "mkdocs" },
{ name = "properdocs" },
]
-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://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "mkdocstrings"
version = "1.0.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jinja2" },
{ name = "markdown" },
@@ -1997,39 +2090,39 @@ dependencies = [
{ 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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.5"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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/b1/b6/e858701499d57eee8b3fd8e78168083956c6683ddbe727b46758b19e1119/mkdocstrings_python-2.0.5.tar.gz", hash = "sha256:3a4d92556ad39637e88af94a5374213af9a8e3040c3824ceaed04b486c017594", size = 199578, upload-time = "2026-06-19T10:41:08.868Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/b6/e858701499d57eee8b3fd8e78168083956c6683ddbe727b46758b19e1119/mkdocstrings_python-2.0.5.tar.gz", hash = "sha256:3a4d92556ad39637e88af94a5374213af9a8e3040c3824ceaed04b486c017594", size = 199578, upload-time = "2026-06-19T10:41:08.868Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d1/fc/10ab7e80650a9c9e8f4f1105f8c8e73567f88ed0c06ada589ab81d38687c/mkdocstrings_python-2.0.5-py3-none-any.whl", hash = "sha256:30c837bbff016549f659fcba6539ac351303f0fd7e713c89a040611072236e9d", size = 104951, upload-time = "2026-06-19T10:41:07.378Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/fc/10ab7e80650a9c9e8f4f1105f8c8e73567f88ed0c06ada589ab81d38687c/mkdocstrings_python-2.0.5-py3-none-any.whl", hash = "sha256:30c837bbff016549f659fcba6539ac351303f0fd7e713c89a040611072236e9d", size = 104951, upload-time = "2026-06-19T10:41:07.378Z" },
]
[[package]]
name = "more-itertools"
version = "11.1.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
]
[[package]]
name = "moto"
version = "5.2.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "boto3" },
{ name = "botocore" },
@@ -2039,9 +2132,9 @@ dependencies = [
{ name = "werkzeug" },
{ name = "xmltodict" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/47/63/d944f387582cc53f53febbff2b3fa36a6d2ed7c1feef8990bf646cfa9cba/moto-5.2.2.tar.gz", hash = "sha256:aac8023a429e125e91c91f8f4730a67b54f518cda587352f7e67252fe3168f75", size = 8678761, upload-time = "2026-06-06T18:57:54.931Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/63/d944f387582cc53f53febbff2b3fa36a6d2ed7c1feef8990bf646cfa9cba/moto-5.2.2.tar.gz", hash = "sha256:aac8023a429e125e91c91f8f4730a67b54f518cda587352f7e67252fe3168f75", size = 8678761, upload-time = "2026-06-06T18:57:54.931Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c1/45/13cff46f4f617a6e97e1d497d75abd913e250bb4c823a4985668c6e593e4/moto-5.2.2-py3-none-any.whl", hash = "sha256:3817f1e39721ca833579b921e53e3b68547ace6a34d848c9486fbb5905808de9", size = 6698689, upload-time = "2026-06-06T18:57:51.435Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/45/13cff46f4f617a6e97e1d497d75abd913e250bb4c823a4985668c6e593e4/moto-5.2.2-py3-none-any.whl", hash = "sha256:3817f1e39721ca833579b921e53e3b68547ace6a34d848c9486fbb5905808de9", size = 6698689, upload-time = "2026-06-06T18:57:51.435Z" },
]
[package.optional-dependencies]
@@ -2061,185 +2154,192 @@ all = [
{ name = "pyyaml" },
{ name = "setuptools" },
]
+dynamodb = [
+ { name = "docker" },
+ { name = "py-partiql-parser" },
+]
+ssm = [
+ { name = "pyyaml" },
+]
[[package]]
name = "mpmath"
version = "1.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
]
[[package]]
name = "msgpack"
version = "1.2.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/5b/16/f70100614b69feb3ade7285f08c9c52d6cda0a5c03f3f5e2facd63acb211/msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c", size = 82926, upload-time = "2026-06-18T16:12:31.531Z" },
- { url = "https://files.pythonhosted.org/packages/e4/3c/08ecd5cdfe4e2de43aec79062028ad0f7b2d9b1fea5430068c198ba570da/msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895", size = 82730, upload-time = "2026-06-18T16:12:32.894Z" },
- { url = "https://files.pythonhosted.org/packages/19/9f/a70c9cb1a04ecc134005149367dcfe35d167284e8f65035a1e4156ad17b5/msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203", size = 400729, upload-time = "2026-06-18T16:12:34.052Z" },
- { url = "https://files.pythonhosted.org/packages/fa/7f/5ce020168cf0439041526e95aa068c722c016aee21624e331aeabeee2e8e/msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73", size = 407625, upload-time = "2026-06-18T16:12:35.239Z" },
- { url = "https://files.pythonhosted.org/packages/79/70/fb7668ce0386819303047057aef6fc1da73b584291d9cff82b821744e2ef/msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833", size = 377891, upload-time = "2026-06-18T16:12:36.684Z" },
- { url = "https://files.pythonhosted.org/packages/3d/dc/9ebe654a73c3aed2e40aa6b52e3c2a02b5f53ef0085fa235a45d5b367f87/msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8", size = 391987, upload-time = "2026-06-18T16:12:37.839Z" },
- { url = "https://files.pythonhosted.org/packages/42/eb/b67cf64218a2fa25e1c671fe1d3dbb06cbeb973e71bc4b822da079862d0b/msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7", size = 374603, upload-time = "2026-06-18T16:12:39.221Z" },
- { url = "https://files.pythonhosted.org/packages/a2/2e/9ee200cde32fd1a0101b4006202fde554c1860adfb9bf7bff31ea4c08df8/msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce", size = 405121, upload-time = "2026-06-18T16:12:40.524Z" },
- { url = "https://files.pythonhosted.org/packages/43/b6/f10117be7ca7a51e8feed699a907b8e663a8cd66e115ae6b4fb30cc7945c/msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74", size = 64088, upload-time = "2026-06-18T16:12:41.762Z" },
- { url = "https://files.pythonhosted.org/packages/ba/93/89976c696fb0224662239d952c47b4d1661b34d79a332ef5584facaa8579/msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb", size = 70113, upload-time = "2026-06-18T16:12:42.78Z" },
- { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" },
- { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" },
- { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" },
- { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" },
- { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" },
- { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" },
- { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" },
- { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" },
- { url = "https://files.pythonhosted.org/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" },
- { url = "https://files.pythonhosted.org/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" },
- { url = "https://files.pythonhosted.org/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" },
- { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" },
- { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" },
- { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" },
- { url = "https://files.pythonhosted.org/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a", size = 419989, upload-time = "2026-06-18T16:13:01.776Z" },
- { url = "https://files.pythonhosted.org/packages/5a/cc/85039b7b0eb168aaad7383a23c97e291a11f08351cb45a606ce865e4e3f1/msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6", size = 377544, upload-time = "2026-06-18T16:13:03.637Z" },
- { url = "https://files.pythonhosted.org/packages/ed/bf/35963899493b32030c85fc513b723ae66144ac70c11ebc52e889e16e3d99/msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a", size = 400842, upload-time = "2026-06-18T16:13:05.012Z" },
- { url = "https://files.pythonhosted.org/packages/a6/df/8e2ac970c8f99264cd9997d1c73df5466bc19da3301d7dc5500862a9b089/msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1", size = 374108, upload-time = "2026-06-18T16:13:06.232Z" },
- { url = "https://files.pythonhosted.org/packages/17/dd/fa8bd265110dfa51c20cb529f9e6d240a16fafe7e645004c6af2d01353ba/msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64", size = 414939, upload-time = "2026-06-18T16:13:07.478Z" },
- { url = "https://files.pythonhosted.org/packages/2e/b9/8377a5ad8953fc0437c70cc98d9ae29f27fe5ac5109fbec0812085865735/msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac", size = 64504, upload-time = "2026-06-18T16:13:08.822Z" },
- { url = "https://files.pythonhosted.org/packages/57/7f/ce1e377df7e62461fefd9eb23bfb93a4a523f40a517b377b8f844d836828/msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24", size = 71421, upload-time = "2026-06-18T16:13:09.828Z" },
- { url = "https://files.pythonhosted.org/packages/8f/32/ebfe84c9929f08f188d56c7a2fd913406a9ddad76a634697c1c43b8112e6/msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07", size = 64775, upload-time = "2026-06-18T16:13:11.056Z" },
- { url = "https://files.pythonhosted.org/packages/b0/ac/dcddcab6f6c20ecb387ca5e980371cdb3f87ff69aeca388be97eebc4c074/msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064", size = 83151, upload-time = "2026-06-18T16:13:12.173Z" },
- { url = "https://files.pythonhosted.org/packages/64/71/fbcfa83a1d6a9c6091942d1cfd070962244664b87427a9a49a6897b1b219/msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056", size = 82351, upload-time = "2026-06-18T16:13:13.194Z" },
- { url = "https://files.pythonhosted.org/packages/e3/10/ddf7b06db879e8792d13934ddda09ff20bd2a583fd84c9b59aae9b0e650b/msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc", size = 407518, upload-time = "2026-06-18T16:13:14.233Z" },
- { url = "https://files.pythonhosted.org/packages/79/d3/36a46a8ed992b781acbc05928bd5bee3c810cb0c3563bf81a7b0c04a1a76/msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d", size = 416405, upload-time = "2026-06-18T16:13:15.435Z" },
- { url = "https://files.pythonhosted.org/packages/f9/84/e8e9598b557c0ba6ddae901a73780a4c75ac667dddf59414b1e56a42fb34/msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155", size = 376257, upload-time = "2026-06-18T16:13:17.022Z" },
- { url = "https://files.pythonhosted.org/packages/40/16/738fe6d875ad7e2a9429c165322a4ec088f4f273cdfae63d96a89c467961/msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402", size = 397469, upload-time = "2026-06-18T16:13:18.287Z" },
- { url = "https://files.pythonhosted.org/packages/ca/be/6d5952df75a7f24f35833af764c3a6860780364cb3a0030beb8099e1b2b4/msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c", size = 372802, upload-time = "2026-06-18T16:13:19.685Z" },
- { url = "https://files.pythonhosted.org/packages/e1/39/e2ef7dbf0473bcb8dc7c50bf782a892d67414877b63e47fc88eb189ef5e6/msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6", size = 411273, upload-time = "2026-06-18T16:13:21.028Z" },
- { url = "https://files.pythonhosted.org/packages/ef/c5/133f4512a56e983a93445c836c9d94d88f3bc2e0980ff4b9e577bd8416ce/msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707", size = 64471, upload-time = "2026-06-18T16:13:22.293Z" },
- { url = "https://files.pythonhosted.org/packages/e2/98/577e10b055096a7dd40732358cabaf7180a20c79ed1dcdbb618e4b9deac7/msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9", size = 71274, upload-time = "2026-06-18T16:13:23.455Z" },
- { url = "https://files.pythonhosted.org/packages/ba/ee/0c0048e7cfbef23c6a94791b8959ab28155232e7956de8a305b5ff588f05/msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a", size = 64795, upload-time = "2026-06-18T16:13:24.687Z" },
- { url = "https://files.pythonhosted.org/packages/77/58/cce442852c6b9e1639c7c8ac8fd9143121cb32dab0f308df4d1426a8eb9c/msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d", size = 83610, upload-time = "2026-06-18T16:13:25.724Z" },
- { url = "https://files.pythonhosted.org/packages/60/5c/15b4c7a0182f75ffa90751958ba36a9c01cafee367d49a3edc10ed140b01/msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7", size = 83138, upload-time = "2026-06-18T16:13:26.781Z" },
- { url = "https://files.pythonhosted.org/packages/b8/a6/99e58722feaffc5f2fbcc0c8c0d1451ab9f84097f7af87291b46af2390f4/msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889", size = 406090, upload-time = "2026-06-18T16:13:28.072Z" },
- { url = "https://files.pythonhosted.org/packages/19/03/8c63e8cf52958534ef688625965ab04c269a6cadd8caef16758b380a821a/msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720", size = 412106, upload-time = "2026-06-18T16:13:29.427Z" },
- { url = "https://files.pythonhosted.org/packages/63/d2/155d9e71b40e41fd934bc0c48b9b2770f22263e1ac20aad8e29fdca7be3f/msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190", size = 374851, upload-time = "2026-06-18T16:13:30.631Z" },
- { url = "https://files.pythonhosted.org/packages/98/48/deaf2326262a8d5ea3295ce9649912ecd3f551ba7ec8e33c665d2ba583f3/msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d", size = 396168, upload-time = "2026-06-18T16:13:31.977Z" },
- { url = "https://files.pythonhosted.org/packages/10/2a/b4410f906c2ec0008f1608d3ab5143afc3ad3f4e6da0fed3ea2231d0bef4/msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24", size = 371959, upload-time = "2026-06-18T16:13:33.282Z" },
- { url = "https://files.pythonhosted.org/packages/59/86/1edc67270099a528fa2093ea60fe191233cd238e4bd30cfacf7db79fc959/msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7", size = 408457, upload-time = "2026-06-18T16:13:34.567Z" },
- { url = "https://files.pythonhosted.org/packages/82/90/8b630fef07d8c5ab457b71ff2c217910c83d333c7a68472c186e87cc504a/msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb", size = 65942, upload-time = "2026-06-18T16:13:36.056Z" },
- { url = "https://files.pythonhosted.org/packages/16/f1/467b81e98b24dd3885d7b1857728797b4ffc76a7a7483af4fb321a07de3c/msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b", size = 72627, upload-time = "2026-06-18T16:13:37.079Z" },
- { url = "https://files.pythonhosted.org/packages/a7/1d/5d8c4c89985feb6acefb82a09e501c60392261856d2408d20bfe4f0360b1/msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7", size = 66908, upload-time = "2026-06-18T16:13:38.23Z" },
- { url = "https://files.pythonhosted.org/packages/1b/02/ad2afb678b4de94496cd432b581759b756a92c1192d8c767edd6b132efdc/msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273", size = 86000, upload-time = "2026-06-18T16:13:39.44Z" },
- { url = "https://files.pythonhosted.org/packages/54/74/0b797484013128837f3b1cbb6cea019277c4de4e377dc512b4d9a0f92940/msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1", size = 86544, upload-time = "2026-06-18T16:13:40.447Z" },
- { url = "https://files.pythonhosted.org/packages/a9/b4/b774d7eb95561739907fec675582f83203cf41c597a418c2589b4bfb8e9d/msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc", size = 427661, upload-time = "2026-06-18T16:13:41.574Z" },
- { url = "https://files.pythonhosted.org/packages/b2/f9/3243191dc9937e00756c8bc1b0272fed8f23758e43df2a3b46f533e5090f/msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde", size = 426375, upload-time = "2026-06-18T16:13:42.936Z" },
- { url = "https://files.pythonhosted.org/packages/23/c7/1693111db9944ba4ad4b67a1e788400d78a0b6af7a6523dc7e4e58f8274b/msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4", size = 380495, upload-time = "2026-06-18T16:13:44.306Z" },
- { url = "https://files.pythonhosted.org/packages/3e/2b/92f86956a0c13e8662f7e2ad630c4eb4db07497b967589bd5245e018b2c1/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d", size = 410897, upload-time = "2026-06-18T16:13:45.629Z" },
- { url = "https://files.pythonhosted.org/packages/da/ea/1479f72d200313a76fc2f823a79d1e07ed052ab7b8a0280640aa7b95de42/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355", size = 378519, upload-time = "2026-06-18T16:13:46.998Z" },
- { url = "https://files.pythonhosted.org/packages/f5/4d/fa006060ffa1011d32bfae826fe766fe73e02982183601633b7121058ab3/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c", size = 419815, upload-time = "2026-06-18T16:13:48.205Z" },
- { url = "https://files.pythonhosted.org/packages/2f/e1/aab6c946570496b78e67804721f3d5e2d62a93081b9b37df77764ef56347/msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1", size = 70914, upload-time = "2026-06-18T16:13:49.385Z" },
- { url = "https://files.pythonhosted.org/packages/13/0a/e608956488a2af014cfe6e3d665e090b8ee42aa14b07f8f95b8880d66b09/msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2", size = 77999, upload-time = "2026-06-18T16:13:50.467Z" },
- { url = "https://files.pythonhosted.org/packages/d2/8a/27e2e57055176e366a46b85d02d68e7a5bcfbdd8474c9706375d965f24d3/msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107", size = 71160, upload-time = "2026-06-18T16:13:51.498Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/16/f70100614b69feb3ade7285f08c9c52d6cda0a5c03f3f5e2facd63acb211/msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c", size = 82926, upload-time = "2026-06-18T16:12:31.531Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e4/3c/08ecd5cdfe4e2de43aec79062028ad0f7b2d9b1fea5430068c198ba570da/msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895", size = 82730, upload-time = "2026-06-18T16:12:32.894Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/9f/a70c9cb1a04ecc134005149367dcfe35d167284e8f65035a1e4156ad17b5/msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203", size = 400729, upload-time = "2026-06-18T16:12:34.052Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/7f/5ce020168cf0439041526e95aa068c722c016aee21624e331aeabeee2e8e/msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73", size = 407625, upload-time = "2026-06-18T16:12:35.239Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/79/70/fb7668ce0386819303047057aef6fc1da73b584291d9cff82b821744e2ef/msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833", size = 377891, upload-time = "2026-06-18T16:12:36.684Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/dc/9ebe654a73c3aed2e40aa6b52e3c2a02b5f53ef0085fa235a45d5b367f87/msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8", size = 391987, upload-time = "2026-06-18T16:12:37.839Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/eb/b67cf64218a2fa25e1c671fe1d3dbb06cbeb973e71bc4b822da079862d0b/msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7", size = 374603, upload-time = "2026-06-18T16:12:39.221Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a2/2e/9ee200cde32fd1a0101b4006202fde554c1860adfb9bf7bff31ea4c08df8/msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce", size = 405121, upload-time = "2026-06-18T16:12:40.524Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/43/b6/f10117be7ca7a51e8feed699a907b8e663a8cd66e115ae6b4fb30cc7945c/msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74", size = 64088, upload-time = "2026-06-18T16:12:41.762Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/93/89976c696fb0224662239d952c47b4d1661b34d79a332ef5584facaa8579/msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb", size = 70113, upload-time = "2026-06-18T16:12:42.78Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a", size = 419989, upload-time = "2026-06-18T16:13:01.776Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/cc/85039b7b0eb168aaad7383a23c97e291a11f08351cb45a606ce865e4e3f1/msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6", size = 377544, upload-time = "2026-06-18T16:13:03.637Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/bf/35963899493b32030c85fc513b723ae66144ac70c11ebc52e889e16e3d99/msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a", size = 400842, upload-time = "2026-06-18T16:13:05.012Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a6/df/8e2ac970c8f99264cd9997d1c73df5466bc19da3301d7dc5500862a9b089/msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1", size = 374108, upload-time = "2026-06-18T16:13:06.232Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/17/dd/fa8bd265110dfa51c20cb529f9e6d240a16fafe7e645004c6af2d01353ba/msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64", size = 414939, upload-time = "2026-06-18T16:13:07.478Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2e/b9/8377a5ad8953fc0437c70cc98d9ae29f27fe5ac5109fbec0812085865735/msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac", size = 64504, upload-time = "2026-06-18T16:13:08.822Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/7f/ce1e377df7e62461fefd9eb23bfb93a4a523f40a517b377b8f844d836828/msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24", size = 71421, upload-time = "2026-06-18T16:13:09.828Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8f/32/ebfe84c9929f08f188d56c7a2fd913406a9ddad76a634697c1c43b8112e6/msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07", size = 64775, upload-time = "2026-06-18T16:13:11.056Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b0/ac/dcddcab6f6c20ecb387ca5e980371cdb3f87ff69aeca388be97eebc4c074/msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064", size = 83151, upload-time = "2026-06-18T16:13:12.173Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/71/fbcfa83a1d6a9c6091942d1cfd070962244664b87427a9a49a6897b1b219/msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056", size = 82351, upload-time = "2026-06-18T16:13:13.194Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/10/ddf7b06db879e8792d13934ddda09ff20bd2a583fd84c9b59aae9b0e650b/msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc", size = 407518, upload-time = "2026-06-18T16:13:14.233Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/79/d3/36a46a8ed992b781acbc05928bd5bee3c810cb0c3563bf81a7b0c04a1a76/msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d", size = 416405, upload-time = "2026-06-18T16:13:15.435Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f9/84/e8e9598b557c0ba6ddae901a73780a4c75ac667dddf59414b1e56a42fb34/msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155", size = 376257, upload-time = "2026-06-18T16:13:17.022Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/16/738fe6d875ad7e2a9429c165322a4ec088f4f273cdfae63d96a89c467961/msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402", size = 397469, upload-time = "2026-06-18T16:13:18.287Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ca/be/6d5952df75a7f24f35833af764c3a6860780364cb3a0030beb8099e1b2b4/msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c", size = 372802, upload-time = "2026-06-18T16:13:19.685Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e1/39/e2ef7dbf0473bcb8dc7c50bf782a892d67414877b63e47fc88eb189ef5e6/msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6", size = 411273, upload-time = "2026-06-18T16:13:21.028Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/c5/133f4512a56e983a93445c836c9d94d88f3bc2e0980ff4b9e577bd8416ce/msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707", size = 64471, upload-time = "2026-06-18T16:13:22.293Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e2/98/577e10b055096a7dd40732358cabaf7180a20c79ed1dcdbb618e4b9deac7/msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9", size = 71274, upload-time = "2026-06-18T16:13:23.455Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/ee/0c0048e7cfbef23c6a94791b8959ab28155232e7956de8a305b5ff588f05/msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a", size = 64795, upload-time = "2026-06-18T16:13:24.687Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/77/58/cce442852c6b9e1639c7c8ac8fd9143121cb32dab0f308df4d1426a8eb9c/msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d", size = 83610, upload-time = "2026-06-18T16:13:25.724Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/5c/15b4c7a0182f75ffa90751958ba36a9c01cafee367d49a3edc10ed140b01/msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7", size = 83138, upload-time = "2026-06-18T16:13:26.781Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/a6/99e58722feaffc5f2fbcc0c8c0d1451ab9f84097f7af87291b46af2390f4/msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889", size = 406090, upload-time = "2026-06-18T16:13:28.072Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/03/8c63e8cf52958534ef688625965ab04c269a6cadd8caef16758b380a821a/msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720", size = 412106, upload-time = "2026-06-18T16:13:29.427Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/d2/155d9e71b40e41fd934bc0c48b9b2770f22263e1ac20aad8e29fdca7be3f/msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190", size = 374851, upload-time = "2026-06-18T16:13:30.631Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/98/48/deaf2326262a8d5ea3295ce9649912ecd3f551ba7ec8e33c665d2ba583f3/msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d", size = 396168, upload-time = "2026-06-18T16:13:31.977Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/10/2a/b4410f906c2ec0008f1608d3ab5143afc3ad3f4e6da0fed3ea2231d0bef4/msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24", size = 371959, upload-time = "2026-06-18T16:13:33.282Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/59/86/1edc67270099a528fa2093ea60fe191233cd238e4bd30cfacf7db79fc959/msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7", size = 408457, upload-time = "2026-06-18T16:13:34.567Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/82/90/8b630fef07d8c5ab457b71ff2c217910c83d333c7a68472c186e87cc504a/msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb", size = 65942, upload-time = "2026-06-18T16:13:36.056Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/f1/467b81e98b24dd3885d7b1857728797b4ffc76a7a7483af4fb321a07de3c/msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b", size = 72627, upload-time = "2026-06-18T16:13:37.079Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a7/1d/5d8c4c89985feb6acefb82a09e501c60392261856d2408d20bfe4f0360b1/msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7", size = 66908, upload-time = "2026-06-18T16:13:38.23Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1b/02/ad2afb678b4de94496cd432b581759b756a92c1192d8c767edd6b132efdc/msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273", size = 86000, upload-time = "2026-06-18T16:13:39.44Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/54/74/0b797484013128837f3b1cbb6cea019277c4de4e377dc512b4d9a0f92940/msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1", size = 86544, upload-time = "2026-06-18T16:13:40.447Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a9/b4/b774d7eb95561739907fec675582f83203cf41c597a418c2589b4bfb8e9d/msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc", size = 427661, upload-time = "2026-06-18T16:13:41.574Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b2/f9/3243191dc9937e00756c8bc1b0272fed8f23758e43df2a3b46f533e5090f/msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde", size = 426375, upload-time = "2026-06-18T16:13:42.936Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/23/c7/1693111db9944ba4ad4b67a1e788400d78a0b6af7a6523dc7e4e58f8274b/msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4", size = 380495, upload-time = "2026-06-18T16:13:44.306Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3e/2b/92f86956a0c13e8662f7e2ad630c4eb4db07497b967589bd5245e018b2c1/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d", size = 410897, upload-time = "2026-06-18T16:13:45.629Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/ea/1479f72d200313a76fc2f823a79d1e07ed052ab7b8a0280640aa7b95de42/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355", size = 378519, upload-time = "2026-06-18T16:13:46.998Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f5/4d/fa006060ffa1011d32bfae826fe766fe73e02982183601633b7121058ab3/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c", size = 419815, upload-time = "2026-06-18T16:13:48.205Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2f/e1/aab6c946570496b78e67804721f3d5e2d62a93081b9b37df77764ef56347/msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1", size = 70914, upload-time = "2026-06-18T16:13:49.385Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/13/0a/e608956488a2af014cfe6e3d665e090b8ee42aa14b07f8f95b8880d66b09/msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2", size = 77999, upload-time = "2026-06-18T16:13:50.467Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/8a/27e2e57055176e366a46b85d02d68e7a5bcfbdd8474c9706375d965f24d3/msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107", size = 71160, upload-time = "2026-06-18T16:13:51.498Z" },
]
[[package]]
name = "multipart"
version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8e/d6/9c4f366d6f9bb8f8fb5eae3acac471335c39510c42b537fd515213d7d8c3/multipart-1.3.1.tar.gz", hash = "sha256:211d7cfc1a7a43e75c4d24ee0e8e0f4f61d522f1a21575303ae85333dea687bf", size = 38929, upload-time = "2026-02-27T10:17:13.7Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/d6/9c4f366d6f9bb8f8fb5eae3acac471335c39510c42b537fd515213d7d8c3/multipart-1.3.1.tar.gz", hash = "sha256:211d7cfc1a7a43e75c4d24ee0e8e0f4f61d522f1a21575303ae85333dea687bf", size = 38929, upload-time = "2026-02-27T10:17:13.7Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/19/ed/e1f03200ee1f0bf4a2b9b72709afefbf5319b68df654e0b84b35c65613ee/multipart-1.3.1-py3-none-any.whl", hash = "sha256:a82b59e1befe74d3d30b3d3f70efd5a2eba4d938f845dcff9faace968888ff29", size = 15061, upload-time = "2026-02-27T10:17:11.943Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/ed/e1f03200ee1f0bf4a2b9b72709afefbf5319b68df654e0b84b35c65613ee/multipart-1.3.1-py3-none-any.whl", hash = "sha256:a82b59e1befe74d3d30b3d3f70efd5a2eba4d938f845dcff9faace968888ff29", size = 15061, upload-time = "2026-02-27T10:17:11.943Z" },
]
[[package]]
name = "networkx"
version = "3.4.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
resolution-markers = [
"python_full_version < '3.11'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" },
]
[[package]]
name = "networkx"
version = "3.6.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
resolution-markers = [
"python_full_version >= '3.12'",
"python_full_version == '3.11.*'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
]
[[package]]
name = "nh3"
version = "0.3.6"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5e/1b/ef84624f14954d270f74060a19fc550dd4f06656399447569afb584d8c06/nh3-0.3.6.tar.gz", hash = "sha256:f3736c9dd3d1856f80cd031715b84ca75cda2bbb1ac802c3da26bfce590838d7", size = 24684, upload-time = "2026-06-22T00:47:02.008Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/99/3e/6506aa4f23dc7b7993a2d0a45dca3ce864ec48380adfe15a173e643c63e8/nh3-0.3.6-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2411e8c3cee81a1ddd62c2a5d50585c28aa5566d373ad1db92536b95ddb24ef2", size = 1421679, upload-time = "2026-06-22T00:46:20.248Z" },
- { url = "https://files.pythonhosted.org/packages/e3/e1/e96e7864a7a53bd6b6fab7e9632467382a2a2c1f3fed951918ad131542fb/nh3-0.3.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e196fa70c2ff2eb4de7d3df3108f8f358c1d69dff20d45b11f20a5aa227ffb6d", size = 792570, upload-time = "2026-06-22T00:46:22.179Z" },
- { url = "https://files.pythonhosted.org/packages/59/62/5b6108bedaef2b2637fed04c87bdbcb5967b9961758b41f0e466ef22a022/nh3-0.3.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34d2b0d934156b87ee114f599a3ba9b8b9e17b5d79652ba3a13fa50903de965e", size = 842243, upload-time = "2026-06-22T00:46:23.801Z" },
- { url = "https://files.pythonhosted.org/packages/4b/4a/526f199626bfcb496bc01a268051b44737962005553b158e985ed7e64865/nh3-0.3.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2f14b7ae1fca99c4a66c981aac3974e7fbc1ca30a12673d223ae1df76680917", size = 1001468, upload-time = "2026-06-22T00:46:25.481Z" },
- { url = "https://files.pythonhosted.org/packages/49/09/0d8e3101636d9ad88cdefb2914e764cb8e876ebdbb4286bfc251277d9c67/nh3-0.3.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:889932a97fb4abb6f95fef1914c0d269ebfb60011e67121c1163059b9449dbb4", size = 1082933, upload-time = "2026-06-22T00:46:27.15Z" },
- { url = "https://files.pythonhosted.org/packages/09/a1/ea83abe738a3fbaa203dfdb836ca7cbab0e7e9609faaee4fe1d4652599c0/nh3-0.3.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edb2b4a1a27523e6cc7c417f8d21ce3d005243548b93e56b762b66b0c7f589f9", size = 1043120, upload-time = "2026-06-22T00:46:28.89Z" },
- { url = "https://files.pythonhosted.org/packages/66/69/0654482b8635012fbae67826bd6c381abb05d841ac7388b9b4666300fdad/nh3-0.3.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43bc1ed3fa0716295fabee29ba42b2667e4a51d140b0a68e092170a765474fa6", size = 1023824, upload-time = "2026-06-22T00:46:30.453Z" },
- { url = "https://files.pythonhosted.org/packages/ed/a6/1f7285ffadc8307c4dbeb08d21b920536d5117785056d1079e998c4dfa44/nh3-0.3.6-cp314-cp314t-win32.whl", hash = "sha256:597a8e843bea00b2eb5520658dc24a9bb032e7fc9e7c2c0c4cd29420220c9796", size = 599253, upload-time = "2026-06-22T00:46:32.072Z" },
- { url = "https://files.pythonhosted.org/packages/36/ea/5542f3c45da4c00290d9d67a65e996702e23e613c4b627de3e09cb9fe357/nh3-0.3.6-cp314-cp314t-win_amd64.whl", hash = "sha256:4713502748f564fee0633b37b3403783ce0a3af3a3d148ad91025a5bdadb7bc6", size = 612553, upload-time = "2026-06-22T00:46:33.53Z" },
- { url = "https://files.pythonhosted.org/packages/66/35/26bd47e6af5915a628281dccdac354ddf4e32f7397047894270acd8c9870/nh3-0.3.6-cp314-cp314t-win_arm64.whl", hash = "sha256:69bbb92865a693d909db3a700d3c01537533844d0948c1e9323561ce06ecda41", size = 595151, upload-time = "2026-06-22T00:46:34.878Z" },
- { url = "https://files.pythonhosted.org/packages/f3/ab/a7653bce9a3b204be6a6931767a9e23595807bb84790ce6685e4d7e5bd08/nh3-0.3.6-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a43ebd7543555c3ac1bc353023d0794e75cb76f6f18f19c32e95441496c0cc25", size = 1443564, upload-time = "2026-06-22T00:46:36.66Z" },
- { url = "https://files.pythonhosted.org/packages/41/21/e1084ab18eb589506335c7c7576f2d4643e9a0c0e33983ef0e549a256b96/nh3-0.3.6-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1b160831c9cdb06a6c79c2f9cdb11386602938f9af260d1c457a85add4f6f69", size = 838002, upload-time = "2026-06-22T00:46:38.101Z" },
- { url = "https://files.pythonhosted.org/packages/b0/94/f48d08e6f72a406300fa11d8acd929fea1a80d4bf750fa292cb10785f126/nh3-0.3.6-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d14bf7982e7a77c0c775634c29c07ce08b38a046df73e1c1f139b3e82f18a38e", size = 823045, upload-time = "2026-06-22T00:46:39.495Z" },
- { url = "https://files.pythonhosted.org/packages/25/bb/431615ba1d1d3eb63cde0f974f2114edf863a8a3f6049a12fed23fc241d3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:44673b27010051ab5a5e438a86ec31bbda61d4a77d7e900af6b7be3037c1abae", size = 1093171, upload-time = "2026-06-22T00:46:41.21Z" },
- { url = "https://files.pythonhosted.org/packages/0e/24/a0d80182a18919665fefd19c1c06f1d1df1c9a6455d0252de40c034a0bc3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6b7beece07525dc6e6b0fc2f104442de2ba328360ad00e50cbe2e1fd620447d", size = 1049217, upload-time = "2026-06-22T00:46:42.804Z" },
- { url = "https://files.pythonhosted.org/packages/0a/13/6f1e302ca674ac74362e150848ad56a1be5145391204f74facdb8e94df12/nh3-0.3.6-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:455469a29951edc92bc48b47ac2281c3f2609e6c4f6a047056449f8c2c23facf", size = 917372, upload-time = "2026-06-22T00:46:44.495Z" },
- { url = "https://files.pythonhosted.org/packages/5b/67/314f6151bad77a93d751978a344033e1fc890822f05f0416079338e34231/nh3-0.3.6-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:905f877dc66dd7aea4a76e54bcb26acb5ff8216f720c0017ccf63e0e6035698e", size = 806699, upload-time = "2026-06-22T00:46:45.99Z" },
- { url = "https://files.pythonhosted.org/packages/3c/a6/bfaa00046e58603507dcfc266c4778e3ab7adf68a5dedd73b6274b8d9314/nh3-0.3.6-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:25c733bee928530556b1db0ea46c52cf5aa686146e38e60a6fc7cb801ef91cec", size = 835165, upload-time = "2026-06-22T00:46:47.617Z" },
- { url = "https://files.pythonhosted.org/packages/30/a8/fb2c38845efb703a9173bffdfc745fc64d2b0e55cfc73a3647d2f028250c/nh3-0.3.6-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f90d9a0cfdbee218994fdaaeeb5a0fde62d08f35e4eef0378ec1e2200172fd0", size = 858282, upload-time = "2026-06-22T00:46:49.276Z" },
- { url = "https://files.pythonhosted.org/packages/68/17/06e72a18ee9b572914447338237ca7eb164c0df901f141bc10d1282247a2/nh3-0.3.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:82ca5bf427ad1b216b65ede1a2e2d87dc49bec417ceba0f297213107d3cd9d78", size = 1014328, upload-time = "2026-06-22T00:46:51.026Z" },
- { url = "https://files.pythonhosted.org/packages/11/f9/3966c61455668c08853bf5e33b4bed93c421f3194ce4de896dc248d6f6ce/nh3-0.3.6-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ed5fe84aee7f39db95c214a7421bf0499fbf500fec6d86a4e29bfc37971438", size = 1098207, upload-time = "2026-06-22T00:46:52.674Z" },
- { url = "https://files.pythonhosted.org/packages/19/d3/479cb4ae440424825735d60525b53e3c77fd60fd6e6afc0e984f00eb0178/nh3-0.3.6-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:082675ff87b9385ec430ffe6d5847ba7456cc39b73720cd4add472f9f4cffd56", size = 1056961, upload-time = "2026-06-22T00:46:54.335Z" },
- { url = "https://files.pythonhosted.org/packages/17/0c/6cdb5ee1e127be50dc8391e54bddc1f64e87bf4bfad0c55633320e2e02db/nh3-0.3.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36d06341bd501240d320f5942481ed5e6846136b666e1ba4faf802b78ebc875f", size = 1033829, upload-time = "2026-06-22T00:46:56.258Z" },
- { url = "https://files.pythonhosted.org/packages/e9/55/9de666ad975d6ccd77d799ea0add55ee2347aa81286ce21b2a97c070746b/nh3-0.3.6-cp38-abi3-win32.whl", hash = "sha256:5276ef17bdba9ad8040575c74072008b13aae429436e9d0429e718bb5f90f4da", size = 609081, upload-time = "2026-06-22T00:46:57.665Z" },
- { url = "https://files.pythonhosted.org/packages/82/fa/2b5d684e3edf1e81bfd02d298c78c3e3da77ca1d8a2be3183a79544a7548/nh3-0.3.6-cp38-abi3-win_amd64.whl", hash = "sha256:f338ac7d594c067679f1e99b4f5ec3906842979560f9d8f15d6bdfa39a353b10", size = 624461, upload-time = "2026-06-22T00:46:59.163Z" },
- { url = "https://files.pythonhosted.org/packages/7b/e5/7cafee2f0413ca4cb0ef3bd111e94d408a48810008b283ad8aee00dd1809/nh3-0.3.6-cp38-abi3-win_arm64.whl", hash = "sha256:69f365963f63a1e9bff53bdbb3c542c7c2efed3e163c9d5d83a772a2ac468c21", size = 603060, upload-time = "2026-06-22T00:47:00.596Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5e/1b/ef84624f14954d270f74060a19fc550dd4f06656399447569afb584d8c06/nh3-0.3.6.tar.gz", hash = "sha256:f3736c9dd3d1856f80cd031715b84ca75cda2bbb1ac802c3da26bfce590838d7", size = 24684, upload-time = "2026-06-22T00:47:02.008Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/3e/6506aa4f23dc7b7993a2d0a45dca3ce864ec48380adfe15a173e643c63e8/nh3-0.3.6-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2411e8c3cee81a1ddd62c2a5d50585c28aa5566d373ad1db92536b95ddb24ef2", size = 1421679, upload-time = "2026-06-22T00:46:20.248Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/e1/e96e7864a7a53bd6b6fab7e9632467382a2a2c1f3fed951918ad131542fb/nh3-0.3.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e196fa70c2ff2eb4de7d3df3108f8f358c1d69dff20d45b11f20a5aa227ffb6d", size = 792570, upload-time = "2026-06-22T00:46:22.179Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/59/62/5b6108bedaef2b2637fed04c87bdbcb5967b9961758b41f0e466ef22a022/nh3-0.3.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34d2b0d934156b87ee114f599a3ba9b8b9e17b5d79652ba3a13fa50903de965e", size = 842243, upload-time = "2026-06-22T00:46:23.801Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4b/4a/526f199626bfcb496bc01a268051b44737962005553b158e985ed7e64865/nh3-0.3.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2f14b7ae1fca99c4a66c981aac3974e7fbc1ca30a12673d223ae1df76680917", size = 1001468, upload-time = "2026-06-22T00:46:25.481Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/09/0d8e3101636d9ad88cdefb2914e764cb8e876ebdbb4286bfc251277d9c67/nh3-0.3.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:889932a97fb4abb6f95fef1914c0d269ebfb60011e67121c1163059b9449dbb4", size = 1082933, upload-time = "2026-06-22T00:46:27.15Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/09/a1/ea83abe738a3fbaa203dfdb836ca7cbab0e7e9609faaee4fe1d4652599c0/nh3-0.3.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edb2b4a1a27523e6cc7c417f8d21ce3d005243548b93e56b762b66b0c7f589f9", size = 1043120, upload-time = "2026-06-22T00:46:28.89Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/69/0654482b8635012fbae67826bd6c381abb05d841ac7388b9b4666300fdad/nh3-0.3.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43bc1ed3fa0716295fabee29ba42b2667e4a51d140b0a68e092170a765474fa6", size = 1023824, upload-time = "2026-06-22T00:46:30.453Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/a6/1f7285ffadc8307c4dbeb08d21b920536d5117785056d1079e998c4dfa44/nh3-0.3.6-cp314-cp314t-win32.whl", hash = "sha256:597a8e843bea00b2eb5520658dc24a9bb032e7fc9e7c2c0c4cd29420220c9796", size = 599253, upload-time = "2026-06-22T00:46:32.072Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/36/ea/5542f3c45da4c00290d9d67a65e996702e23e613c4b627de3e09cb9fe357/nh3-0.3.6-cp314-cp314t-win_amd64.whl", hash = "sha256:4713502748f564fee0633b37b3403783ce0a3af3a3d148ad91025a5bdadb7bc6", size = 612553, upload-time = "2026-06-22T00:46:33.53Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/35/26bd47e6af5915a628281dccdac354ddf4e32f7397047894270acd8c9870/nh3-0.3.6-cp314-cp314t-win_arm64.whl", hash = "sha256:69bbb92865a693d909db3a700d3c01537533844d0948c1e9323561ce06ecda41", size = 595151, upload-time = "2026-06-22T00:46:34.878Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/ab/a7653bce9a3b204be6a6931767a9e23595807bb84790ce6685e4d7e5bd08/nh3-0.3.6-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a43ebd7543555c3ac1bc353023d0794e75cb76f6f18f19c32e95441496c0cc25", size = 1443564, upload-time = "2026-06-22T00:46:36.66Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/41/21/e1084ab18eb589506335c7c7576f2d4643e9a0c0e33983ef0e549a256b96/nh3-0.3.6-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1b160831c9cdb06a6c79c2f9cdb11386602938f9af260d1c457a85add4f6f69", size = 838002, upload-time = "2026-06-22T00:46:38.101Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b0/94/f48d08e6f72a406300fa11d8acd929fea1a80d4bf750fa292cb10785f126/nh3-0.3.6-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d14bf7982e7a77c0c775634c29c07ce08b38a046df73e1c1f139b3e82f18a38e", size = 823045, upload-time = "2026-06-22T00:46:39.495Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/bb/431615ba1d1d3eb63cde0f974f2114edf863a8a3f6049a12fed23fc241d3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:44673b27010051ab5a5e438a86ec31bbda61d4a77d7e900af6b7be3037c1abae", size = 1093171, upload-time = "2026-06-22T00:46:41.21Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0e/24/a0d80182a18919665fefd19c1c06f1d1df1c9a6455d0252de40c034a0bc3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6b7beece07525dc6e6b0fc2f104442de2ba328360ad00e50cbe2e1fd620447d", size = 1049217, upload-time = "2026-06-22T00:46:42.804Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0a/13/6f1e302ca674ac74362e150848ad56a1be5145391204f74facdb8e94df12/nh3-0.3.6-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:455469a29951edc92bc48b47ac2281c3f2609e6c4f6a047056449f8c2c23facf", size = 917372, upload-time = "2026-06-22T00:46:44.495Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/67/314f6151bad77a93d751978a344033e1fc890822f05f0416079338e34231/nh3-0.3.6-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:905f877dc66dd7aea4a76e54bcb26acb5ff8216f720c0017ccf63e0e6035698e", size = 806699, upload-time = "2026-06-22T00:46:45.99Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/a6/bfaa00046e58603507dcfc266c4778e3ab7adf68a5dedd73b6274b8d9314/nh3-0.3.6-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:25c733bee928530556b1db0ea46c52cf5aa686146e38e60a6fc7cb801ef91cec", size = 835165, upload-time = "2026-06-22T00:46:47.617Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/a8/fb2c38845efb703a9173bffdfc745fc64d2b0e55cfc73a3647d2f028250c/nh3-0.3.6-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f90d9a0cfdbee218994fdaaeeb5a0fde62d08f35e4eef0378ec1e2200172fd0", size = 858282, upload-time = "2026-06-22T00:46:49.276Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/17/06e72a18ee9b572914447338237ca7eb164c0df901f141bc10d1282247a2/nh3-0.3.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:82ca5bf427ad1b216b65ede1a2e2d87dc49bec417ceba0f297213107d3cd9d78", size = 1014328, upload-time = "2026-06-22T00:46:51.026Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/11/f9/3966c61455668c08853bf5e33b4bed93c421f3194ce4de896dc248d6f6ce/nh3-0.3.6-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ed5fe84aee7f39db95c214a7421bf0499fbf500fec6d86a4e29bfc37971438", size = 1098207, upload-time = "2026-06-22T00:46:52.674Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/d3/479cb4ae440424825735d60525b53e3c77fd60fd6e6afc0e984f00eb0178/nh3-0.3.6-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:082675ff87b9385ec430ffe6d5847ba7456cc39b73720cd4add472f9f4cffd56", size = 1056961, upload-time = "2026-06-22T00:46:54.335Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/17/0c/6cdb5ee1e127be50dc8391e54bddc1f64e87bf4bfad0c55633320e2e02db/nh3-0.3.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36d06341bd501240d320f5942481ed5e6846136b666e1ba4faf802b78ebc875f", size = 1033829, upload-time = "2026-06-22T00:46:56.258Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e9/55/9de666ad975d6ccd77d799ea0add55ee2347aa81286ce21b2a97c070746b/nh3-0.3.6-cp38-abi3-win32.whl", hash = "sha256:5276ef17bdba9ad8040575c74072008b13aae429436e9d0429e718bb5f90f4da", size = 609081, upload-time = "2026-06-22T00:46:57.665Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/82/fa/2b5d684e3edf1e81bfd02d298c78c3e3da77ca1d8a2be3183a79544a7548/nh3-0.3.6-cp38-abi3-win_amd64.whl", hash = "sha256:f338ac7d594c067679f1e99b4f5ec3906842979560f9d8f15d6bdfa39a353b10", size = 624461, upload-time = "2026-06-22T00:46:59.163Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/e5/7cafee2f0413ca4cb0ef3bd111e94d408a48810008b283ad8aee00dd1809/nh3-0.3.6-cp38-abi3-win_arm64.whl", hash = "sha256:69f365963f63a1e9bff53bdbb3c542c7c2efed3e163c9d5d83a772a2ac468c21", size = 603060, upload-time = "2026-06-22T00:47:00.596Z" },
]
[[package]]
name = "nltk"
version = "3.9.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "click" },
{ name = "joblib" },
{ name = "regex" },
{ name = "tqdm" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" },
]
[[package]]
name = "nodeenv"
version = "1.10.0"
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "openapi-schema-validator"
-version = "0.9.0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.8.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jsonschema" },
{ name = "jsonschema-specifications" },
@@ -2248,15 +2348,15 @@ dependencies = [
{ name = "referencing" },
{ name = "rfc3339-validator" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/98/e8/ab3f27dbca54ec645f7fab714b640907d5d36c2ebb07e87eebd30bd5c81b/openapi_schema_validator-0.9.0.tar.gz", hash = "sha256:b72db64315b89d21834cd3ffef37e3e6893bc876327be2d366e8424b1029afd3", size = 24686, upload-time = "2026-04-27T17:31:27.606Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/21/4b/67b24b2b23d96ea862be2cca3632a546f67a22461200831213e80c3c6011/openapi_schema_validator-0.8.1.tar.gz", hash = "sha256:4c57266ce8cbfa37bb4eb4d62cdb7d19356c3a468e3535743c4562863e1790da", size = 23134, upload-time = "2026-03-02T08:46:29.807Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/90/c0/5467967d95378b2cfce312e09cbd0c9ab64354a0922379b734f793edd04f/openapi_schema_validator-0.9.0-py3-none-any.whl", hash = "sha256:faa3bbe7c3aa8ca2087ad83f709dc3b7d920283153a570c03e24ea182558aa25", size = 19980, upload-time = "2026-04-27T17:31:25.965Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/87/e9f29f463b230d4b47d65e17858c595153a8ca8c1775f16e406aa82d455d/openapi_schema_validator-0.8.1-py3-none-any.whl", hash = "sha256:0f5859794c5bfa433d478dc5ac5e5768d50adc56b14380c8a6fd3a8113e89c9b", size = 19211, upload-time = "2026-03-02T08:46:28.154Z" },
]
[[package]]
name = "openapi-spec-validator"
-version = "0.9.0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.8.5"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jsonschema" },
{ name = "jsonschema-path" },
@@ -2265,43 +2365,73 @@ dependencies = [
{ name = "pydantic" },
{ name = "pydantic-settings" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/8f/d2/640b5149cd5688bc0ad1fdbb4df6a2f7b84a093c8d787c27d566132f8b8b/openapi_spec_validator-0.9.0.tar.gz", hash = "sha256:6d648cff6490ebb799dcfe273792f2941c050158854c721f086599d845da78b8", size = 1756839, upload-time = "2026-05-20T09:23:18.871Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/79/3f/aa0c1150627b4e683ae5673486b7d5cf2623a8821601863ee389e430965a/openapi_spec_validator-0.8.5.tar.gz", hash = "sha256:93b04ef5321d5866b2502371123d86333e5c1444f051d323e02525d9e83c7622", size = 1756845, upload-time = "2026-04-24T15:25:21.334Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/95/d8/321ff889330acca2e3097f3d4f80a40bcc41b6d34d302978ab32c449520b/openapi_spec_validator-0.9.0-py3-none-any.whl", hash = "sha256:222fecffc7714f6d0a6ad62c0e4b66cc2b7dbfafb7b93acfc6c308abbdb51af8", size = 50328, upload-time = "2026-05-20T09:23:17.017Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/96/d7dfe1cc0be2df22d7a97ffb0f8bb00b10d92749aa6e64ffa7cc9a041580/openapi_spec_validator-0.8.5-py3-none-any.whl", hash = "sha256:3669106361856934153991e30714616a294865a33f6411a4c25d1dc2d08cfbc2", size = 50334, upload-time = "2026-04-24T15:25:19.65Z" },
]
[[package]]
name = "opentelemetry-api"
-version = "1.40.0"
-source = { registry = "https://pypi.org/simple" }
+version = "1.37.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "importlib-metadata" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "opentelemetry-proto" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/repository/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "opentelemetry-instrumentation"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "packaging" },
{ name = "wrapt" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "opentelemetry-instrumentation-asgi"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "asgiref" },
{ name = "opentelemetry-api" },
@@ -2309,29 +2439,29 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/00/3e/143cf5c034e58037307e6a24f06e0dd64b2c49ae60a965fc580027581931/opentelemetry_instrumentation_asgi-0.61b0.tar.gz", hash = "sha256:9d08e127244361dc33976d39dd4ca8f128b5aa5a7ae425208400a80a095019b5", size = 26691, upload-time = "2026-03-04T14:20:21.038Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/e2/03ff707d881d590c7adaed5e9d1979aed7e5e53fc1ed89035e5ed9f304af/opentelemetry_instrumentation_asgi-0.58b0.tar.gz", hash = "sha256:3ccc0c9c1c8c71e8d9da5945c6dcd9c0c8d147839f208536b7042c6dd98e65c9", size = 25116, upload-time = "2025-09-11T11:42:18.437Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/19/78/154470cf9d741a7487fbb5067357b87386475bbb77948a6707cae982e158/opentelemetry_instrumentation_asgi-0.61b0-py3-none-any.whl", hash = "sha256:e4b3ce6b66074e525e717efff20745434e5efd5d9df6557710856fba356da7a4", size = 16980, upload-time = "2026-03-04T14:19:10.894Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8c/71/a00884c6655387c70070138acbf79a6616ad5d4489680f40708d75b598a7/opentelemetry_instrumentation_asgi-0.58b0-py3-none-any.whl", hash = "sha256:508a6d79e333d648d2afee0e140b6e80eb5d443be183be58e81d9ff88373168a", size = 16798, upload-time = "2025-09-11T11:41:08.105Z" },
]
[[package]]
name = "opentelemetry-instrumentation-boto"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-semantic-conventions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a0/e2/dcf07272aac51758af6c55e0a62f23e645d1f8f54ec41b107f1a3e765ee1/opentelemetry_instrumentation_boto-0.61b0.tar.gz", hash = "sha256:f8066f5b8a32bc0fe98d0416d161cfcf5a4b94f25f351a49c772679a4a5f09d7", size = 9711, upload-time = "2026-03-04T14:20:24.808Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/26/69/764153b11526480568c787cf77c2f0e3d490fe59db4d053bfd1758f639e7/opentelemetry_instrumentation_boto-0.58b0.tar.gz", hash = "sha256:1f9b543c26fae287d7f838edfacbeb0ee18d68da7c9d30cddbc5b5de54e49008", size = 9705, upload-time = "2025-09-11T11:42:22.778Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/99/87/bdfa97c692f2cfc99cd80d39d4469c0816f68ec5ef13049cac2da2f2e641/opentelemetry_instrumentation_boto-0.61b0-py3-none-any.whl", hash = "sha256:d6e8fd937fd47d675b9e98eecff872aa60ed688f4bee75f3c372e74c45440218", size = 10160, upload-time = "2026-03-04T14:19:17.111Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/58/b3/8cbb7d337d1dceb48d6b3f9deed09ccdd9d84d89a0f8ac4cc9ba9788a064/opentelemetry_instrumentation_boto-0.58b0-py3-none-any.whl", hash = "sha256:5f9d7f0a5f587803166167269583ef4c7b2f61ff26a557ae039f60203992d253", size = 10156, upload-time = "2025-09-11T11:41:14.72Z" },
]
[[package]]
name = "opentelemetry-instrumentation-fastapi"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
@@ -2339,15 +2469,30 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/37/35/aa727bb6e6ef930dcdc96a617b83748fece57b43c47d83ba8d83fbeca657/opentelemetry_instrumentation_fastapi-0.61b0.tar.gz", hash = "sha256:3a24f35b07c557ae1bbc483bf8412221f25d79a405f8b047de8b670722e2fa9f", size = 24800, upload-time = "2026-03-04T14:20:32.759Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/09/4f8fcab834af6b403e5e2d94bdfb2d0835ba8cd1049bcc156995f47b65fb/opentelemetry_instrumentation_fastapi-0.58b0.tar.gz", hash = "sha256:03da470d694116a0a40f4e76319e42f3ff9efc49abf804b2acc2c07f96661497", size = 24598, upload-time = "2025-09-11T11:42:35.325Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/91/05/acfeb2cccd434242a0a7d0ea29afaf077e04b42b35b485d89aee4e0d9340/opentelemetry_instrumentation_fastapi-0.61b0-py3-none-any.whl", hash = "sha256:a1a844d846540d687d377516b2ff698b51d87c781b59f47c214359c4a241047c", size = 13485, upload-time = "2026-03-04T14:19:30.351Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/45/fb/82de06eba54e5cb979274f073065ebc374794853502d342b5155073d1194/opentelemetry_instrumentation_fastapi-0.58b0-py3-none-any.whl", hash = "sha256:d89bfec69c9ffc5d9f3fe58655d6660a66b2bca863b9132712c06edcde68b6fa", size = 13460, upload-time = "2025-09-11T11:41:28.507Z" },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation-requests"
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "opentelemetry-util-http" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "opentelemetry-instrumentation-sqlalchemy"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
@@ -2355,45 +2500,71 @@ dependencies = [
{ name = "packaging" },
{ name = "wrapt" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9e/4f/3a325b180944610697a0a926d49d782b41a86120050d44fefb2715b630ac/opentelemetry_instrumentation_sqlalchemy-0.61b0.tar.gz", hash = "sha256:13a3a159a2043a52f0180b3757fbaa26741b0e08abb50deddce4394c118956e6", size = 15343, upload-time = "2026-03-04T14:20:47.648Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/91/6f/fa2c45d5dfb8da0bcc337a421fd12946994cb5e9782c40a21c669e78460d/opentelemetry_instrumentation_sqlalchemy-0.58b0.tar.gz", hash = "sha256:3e4b444a05088ba473710df9d5c730bb08969c8ea71e04f2886a0f7efee22c12", size = 14993, upload-time = "2025-09-11T11:42:52.053Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1f/97/b906a930c6a1a20c53ecc8b58cabc2cdd0ce560a2b5d44259084ffe4333e/opentelemetry_instrumentation_sqlalchemy-0.61b0-py3-none-any.whl", hash = "sha256:f115e0be54116ba4c327b8d7b68db4045ee18d44439d888ab8130a549c50d1c1", size = 14547, upload-time = "2026-03-04T14:19:53.088Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/26/7a86285adf7131801fe297bdb756b00bc3f43f78bc7e18668d88f7308e53/opentelemetry_instrumentation_sqlalchemy-0.58b0-py3-none-any.whl", hash = "sha256:7c11d11887b3a4dbc3fccdd58a24903e306052ff7a59685caea1dd7797f82b0a", size = 14212, upload-time = "2025-09-11T11:41:53.76Z" },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation-threading"
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation" },
+ { name = "wrapt" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "opentelemetry-proto"
+version = "1.37.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "opentelemetry-sdk"
-version = "1.40.0"
-source = { registry = "https://pypi.org/simple" }
+version = "1.37.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "opentelemetry-semantic-conventions"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "opentelemetry-util-http"
-version = "0.61b0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361, upload-time = "2026-03-04T14:20:57.01Z" }
+version = "0.58b0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281, upload-time = "2026-03-04T14:20:08.364Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
@@ -2417,10 +2588,11 @@ dependencies = [
[package.optional-dependencies]
all = [
+ { name = "boto3" },
+ { name = "botocore" },
{ name = "fastapi" },
{ name = "jinja2" },
{ name = "opentelemetry-api" },
- { name = "opentelemetry-instrumentation-boto" },
{ name = "opentelemetry-instrumentation-fastapi" },
{ name = "opentelemetry-instrumentation-sqlalchemy" },
{ name = "opentelemetry-sdk" },
@@ -2431,12 +2603,20 @@ all = [
{ name = "starlette" },
{ name = "uvicorn" },
]
+all-providers = [
+ { name = "boto3" },
+ { name = "botocore" },
+]
api = [
{ name = "fastapi" },
{ name = "jinja2" },
{ name = "starlette" },
{ name = "uvicorn" },
]
+aws = [
+ { name = "boto3" },
+ { name = "botocore" },
+]
ci = [
{ name = "bandit" },
{ name = "bandit-sarif-formatter" },
@@ -2489,6 +2669,8 @@ cli = [
dev = [
{ name = "bandit" },
{ name = "bandit-sarif-formatter" },
+ { name = "boto3" },
+ { name = "botocore" },
{ name = "build" },
{ name = "bump2version" },
{ name = "coverage" },
@@ -2546,6 +2728,16 @@ dev = [
{ name = "wheel" },
]
monitoring = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation-fastapi" },
+ { name = "opentelemetry-instrumentation-sqlalchemy" },
+ { name = "opentelemetry-sdk" },
+ { name = "prometheus-client" },
+ { name = "psutil" },
+]
+monitoring-aws = [
+ { name = "boto3" },
+ { name = "botocore" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation-boto" },
{ name = "opentelemetry-instrumentation-fastapi" },
@@ -2554,6 +2746,13 @@ monitoring = [
{ name = "prometheus-client" },
{ name = "psutil" },
]
+test-aws = [
+ { name = "boto3" },
+ { name = "botocore" },
+ { name = "moto", extra = ["dynamodb", "ssm"] },
+ { name = "requests-mock" },
+ { name = "responses" },
+]
[package.dev-dependencies]
ci = [
@@ -2566,6 +2765,7 @@ ci = [
{ name = "httpx" },
{ name = "jinja2" },
{ name = "joserfc" },
+ { name = "lxml" },
{ name = "marshmallow" },
{ name = "mike" },
{ name = "mkdocs" },
@@ -2616,6 +2816,7 @@ dev = [
{ name = "jinja2" },
{ name = "joserfc" },
{ name = "line-profiler" },
+ { name = "lxml" },
{ name = "marshmallow" },
{ name = "memory-profiler" },
{ name = "mike" },
@@ -2666,7 +2867,9 @@ requires-dist = [
{ name = "bandit", marker = "extra == 'ci'", specifier = ">=1.7.5,<2.0.0" },
{ name = "bandit-sarif-formatter", marker = "extra == 'ci'", specifier = ">=1.1.1,<2.0.0" },
{ name = "boto3", specifier = ">=1.42.21" },
+ { name = "boto3", marker = "extra == 'aws'", specifier = ">=1.42.21" },
{ name = "botocore", specifier = ">=1.42.21" },
+ { name = "botocore", marker = "extra == 'aws'", specifier = ">=1.42.21" },
{ name = "build", marker = "extra == 'ci'", specifier = ">=1.0.3,<2.0.0" },
{ name = "bump2version", marker = "extra == 'dev'", specifier = ">=1.0.1,<2.0.0" },
{ name = "coverage", marker = "extra == 'ci'", specifier = ">=7.3.2,<8.0.0" },
@@ -2695,14 +2898,18 @@ requires-dist = [
{ name = "mkdocstrings", marker = "extra == 'ci'", specifier = ">=0.22.0,<2.0.0" },
{ name = "mkdocstrings-python", marker = "extra == 'ci'", specifier = ">=1.1.0,<3.0.0" },
{ name = "moto", extras = ["all"], marker = "extra == 'ci'", specifier = ">=5.1.19,<6.0.0" },
+ { name = "moto", extras = ["ec2", "iam", "ec2instanceconnect", "autoscaling", "ssm", "dynamodb", "sts", "sqs", "sns"], marker = "extra == 'test-aws'", specifier = ">=5.1.19,<6.0.0" },
{ name = "nltk", marker = "extra == 'ci'", specifier = ">=3.9.3" },
{ name = "opentelemetry-api", marker = "extra == 'monitoring'", specifier = ">=1.20.0" },
- { name = "opentelemetry-instrumentation-boto", marker = "extra == 'monitoring'", specifier = ">=0.41b0" },
+ { name = "opentelemetry-instrumentation-boto", marker = "extra == 'monitoring-aws'", specifier = ">=0.41b0" },
{ name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'monitoring'", specifier = ">=0.41b0" },
{ name = "opentelemetry-instrumentation-sqlalchemy", marker = "extra == 'monitoring'", specifier = ">=0.41b0" },
{ name = "opentelemetry-sdk", marker = "extra == 'monitoring'", specifier = ">=1.20.0" },
- { name = "orb-py", extras = ["ci"], marker = "extra == 'dev'" },
- { name = "orb-py", extras = ["cli", "api", "monitoring"], marker = "extra == 'all'" },
+ { name = "orb-py", extras = ["aws"], marker = "extra == 'all-providers'" },
+ { name = "orb-py", extras = ["aws"], marker = "extra == 'test-aws'" },
+ { name = "orb-py", extras = ["aws", "monitoring"], marker = "extra == 'monitoring-aws'" },
+ { name = "orb-py", extras = ["ci", "all-providers"], marker = "extra == 'dev'" },
+ { name = "orb-py", extras = ["cli", "api", "monitoring", "all-providers"], marker = "extra == 'all'" },
{ name = "pathspec", marker = "extra == 'ci'", specifier = ">=0.11.0" },
{ name = "peewee", marker = "extra == 'ci'", specifier = ">=3.18.3" },
{ name = "pip-audit", marker = "extra == 'ci'", specifier = ">=2.6.1,<3.0.0" },
@@ -2730,7 +2937,9 @@ requires-dist = [
{ name = "radon", marker = "extra == 'dev'", specifier = ">=6.0.1,<7.0.0" },
{ name = "requests", specifier = ">=2.31.0" },
{ name = "requests-mock", marker = "extra == 'ci'", specifier = ">=1.11.0,<2.0.0" },
+ { name = "requests-mock", marker = "extra == 'test-aws'", specifier = ">=1.11.0,<2.0.0" },
{ name = "responses", marker = "extra == 'ci'", specifier = ">=0.24.0,<1.0.0" },
+ { name = "responses", marker = "extra == 'test-aws'", specifier = ">=0.24.0,<1.0.0" },
{ name = "rich", marker = "extra == 'cli'", specifier = ">=13.3.0" },
{ name = "rich-argparse", marker = "extra == 'cli'", specifier = ">=1.0.0" },
{ name = "ruff", marker = "extra == 'ci'", specifier = ">=0.1.0" },
@@ -2748,7 +2957,7 @@ requires-dist = [
{ name = "werkzeug", marker = "extra == 'ci'", specifier = ">=3.1.6" },
{ name = "wheel", marker = "extra == 'ci'", specifier = ">=0.41.3,<1.0.0" },
]
-provides-extras = ["cli", "api", "monitoring", "all", "ci", "dev"]
+provides-extras = ["aws", "all-providers", "cli", "api", "monitoring", "monitoring-aws", "all", "test-aws", "ci", "dev"]
[package.metadata.requires-dev]
ci = [
@@ -2761,6 +2970,7 @@ ci = [
{ name = "httpx", specifier = ">=0.27.0" },
{ name = "jinja2", specifier = ">=3.1.0" },
{ name = "joserfc", specifier = ">=1.6.1" },
+ { name = "lxml", specifier = ">=6.1.0" },
{ name = "marshmallow", specifier = ">=4.1.2" },
{ name = "mike", specifier = ">=1.1.0,<3.0.0" },
{ name = "mkdocs", specifier = ">=1.5.0,<2.0.0" },
@@ -2811,6 +3021,7 @@ dev = [
{ name = "jinja2", specifier = ">=3.1.0" },
{ name = "joserfc", specifier = ">=1.6.1" },
{ name = "line-profiler", specifier = ">=4.1.1,<6.0.0" },
+ { name = "lxml", specifier = ">=6.1.0" },
{ name = "marshmallow", specifier = ">=4.1.2" },
{ name = "memory-profiler", specifier = ">=0.61.0,<1.0.0" },
{ name = "mike", specifier = ">=1.1.0,<3.0.0" },
@@ -2859,115 +3070,115 @@ dev = [
[[package]]
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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "paginate"
version = "0.5.7"
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "parso"
version = "0.8.7"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" },
]
[[package]]
name = "pathable"
-version = "0.6.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/66/f3/5a20387de9bcd0607871bfc2198ee0e15836da7baa4592ccd7f24c27c986/pathable-0.6.0.tar.gz", hash = "sha256:6404b8b82aef5ff0fd478934137128b99b12212ba35afdde5525ca4f8388ea58", size = 18970, upload-time = "2026-05-19T18:15:11.911Z" }
+version = "0.5.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a2/e8/6d75ffd9784bce2e93d1ae4415649427e39a53bb172d4672b2b59c6f0a7b/pathable-0.6.0-py3-none-any.whl", hash = "sha256:82c4ca6c98c502ad12e0d4e9779b6210afee93c38990988c8c5d1b49bdcdf566", size = 18983, upload-time = "2026-05-19T18:15:10.728Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pbr"
version = "7.0.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "setuptools" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5e/ab/1de9a4f730edde1bdbbc2b8d19f8fa326f036b4f18b2f72cfbea7dc53c26/pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29", size = 135625, upload-time = "2025-11-03T17:04:56.274Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5e/ab/1de9a4f730edde1bdbbc2b8d19f8fa326f036b4f18b2f72cfbea7dc53c26/pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29", size = 135625, upload-time = "2025-11-03T17:04:56.274Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c0/db/61efa0d08a99f897ef98256b03e563092d36cc38dc4ebe4a85020fe40b31/pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b", size = 131898, upload-time = "2025-11-03T17:04:54.875Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c0/db/61efa0d08a99f897ef98256b03e563092d36cc38dc4ebe4a85020fe40b31/pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b", size = 131898, upload-time = "2025-11-03T17:04:54.875Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ 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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pip"
version = "26.1.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" },
]
[[package]]
name = "pip-api"
version = "0.0.34"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pip-audit"
-version = "2.9.0"
-source = { registry = "https://pypi.org/simple" }
+version = "2.10.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "cachecontrol", extra = ["filecache"] },
{ name = "cyclonedx-python-lib" },
@@ -2977,30 +3188,31 @@ dependencies = [
{ name = "platformdirs" },
{ name = "requests" },
{ name = "rich" },
- { name = "toml" },
+ { name = "tomli" },
+ { name = "tomli-w" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/cc/7f/28fad19a9806f796f13192ab6974c07c4a04d9cbb8e30dd895c3c11ce7ee/pip_audit-2.9.0.tar.gz", hash = "sha256:0b998410b58339d7a231e5aa004326a294e4c7c6295289cdc9d5e1ef07b1f44d", size = 52089, upload-time = "2025-04-07T16:45:23.679Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/a4/f21d5f0a0edabcbce31560b73c7c5a6f72ae87af4236fd1069c8f59a353d/pip_audit-2.10.1.tar.gz", hash = "sha256:1eb4565d19ebe5d48996f4b770b4d2b32887e12cb12cfa637f1a064011b55ffc", size = 54275, upload-time = "2026-06-10T22:17:01.744Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/43/9e/f4dfd9d3dadb6d6dc9406f1111062f871e2e248ed7b584cca6020baf2ac1/pip_audit-2.9.0-py3-none-any.whl", hash = "sha256:348b16e60895749a0839875d7cc27ebd692e1584ebe5d5cb145941c8e25a80bd", size = 58634, upload-time = "2025-04-07T16:45:22.056Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/a7/b0c504148114047bd1bc9d97447453c6850ca176bb2f3c0038835994e8b7/pip_audit-2.10.1-py3-none-any.whl", hash = "sha256:99ef3f600a317c1945f1e89e227ef26e1c2d618429b8bd3fa6f4f7c440c4611a", size = 62023, upload-time = "2026-06-10T22:17:00.309Z" },
]
[[package]]
name = "pip-requirements-parser"
version = "32.0.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "packaging" },
{ name = "pyparsing" },
]
-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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pip-tools"
version = "7.5.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "build" },
{ name = "click" },
@@ -3010,33 +3222,33 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "wheel" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/4a/db/c6e2a02db5d98aa5f3250a305ce71e8bc3d1a022d1f47a54d14492ae23de/pip_tools-7.5.3.tar.gz", hash = "sha256:8fa364779ebc010cbfe17cb9de404457ac733e100840423f28f6955de7742d41", size = 176153, upload-time = "2026-02-11T18:25:07.72Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4a/db/c6e2a02db5d98aa5f3250a305ce71e8bc3d1a022d1f47a54d14492ae23de/pip_tools-7.5.3.tar.gz", hash = "sha256:8fa364779ebc010cbfe17cb9de404457ac733e100840423f28f6955de7742d41", size = 176153, upload-time = "2026-02-11T18:25:07.72Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6e/74/59906d876c6cb1137f42a137164f2fe683b06283cde84bfcf7f5dd43970b/pip_tools-7.5.3-py3-none-any.whl", hash = "sha256:3aac0c473240ae90db7213c033401f345b05197293ccbdd2704e52e7a783785e", size = 71359, upload-time = "2026-02-11T18:25:06.119Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6e/74/59906d876c6cb1137f42a137164f2fe683b06283cde84bfcf7f5dd43970b/pip_tools-7.5.3-py3-none-any.whl", hash = "sha256:3aac0c473240ae90db7213c033401f345b05197293ccbdd2704e52e7a783785e", size = 71359, upload-time = "2026-02-11T18:25:06.119Z" },
]
[[package]]
name = "platformdirs"
version = "4.10.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pre-commit"
version = "4.6.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "cfgv" },
{ name = "identify" },
@@ -3044,36 +3256,36 @@ dependencies = [
{ name = "pyyaml" },
{ name = "virtualenv" },
]
-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://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" }
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://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]
[[package]]
name = "prometheus-client"
version = "0.25.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" },
]
[[package]]
name = "prompt-toolkit"
version = "3.0.52"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "wcwidth" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
]
[[package]]
name = "properdocs"
version = "1.6.7"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "click" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -3088,325 +3300,345 @@ dependencies = [
{ 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://pkgs.safetycli.com/package/fgo-inc/pypi/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/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "protobuf"
+version = "6.33.6"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "psutil"
version = "7.2.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
- { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
- { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
- { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
- { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
- { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
- { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" },
- { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" },
- { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" },
- { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" },
- { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" },
- { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" },
- { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
- { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
- { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
- { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
- { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
- { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
- { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
- { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
]
[[package]]
name = "ptyprocess"
version = "0.7.0"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pure-eval"
version = "0.2.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
]
[[package]]
name = "py-cpuinfo"
version = "9.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" },
]
[[package]]
name = "py-partiql-parser"
version = "0.6.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/56/7a/a0f6bda783eb4df8e3dfd55973a1ac6d368a89178c300e1b5b91cd181e5e/py_partiql_parser-0.6.3.tar.gz", hash = "sha256:09cecf916ce6e3da2c050f0cb6106166de42c33d34a078ec2eb19377ea70389a", size = 17456, upload-time = "2025-10-18T13:56:13.441Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/56/7a/a0f6bda783eb4df8e3dfd55973a1ac6d368a89178c300e1b5b91cd181e5e/py_partiql_parser-0.6.3.tar.gz", hash = "sha256:09cecf916ce6e3da2c050f0cb6106166de42c33d34a078ec2eb19377ea70389a", size = 17456, upload-time = "2025-10-18T13:56:13.441Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c9/33/a7cbfccc39056a5cf8126b7aab4c8bafbedd4f0ca68ae40ecb627a2d2cd3/py_partiql_parser-0.6.3-py2.py3-none-any.whl", hash = "sha256:deb0769c3346179d2f590dcbde556f708cdb929059fb654bad75f4cf6e07f582", size = 23752, upload-time = "2025-10-18T13:56:12.256Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c9/33/a7cbfccc39056a5cf8126b7aab4c8bafbedd4f0ca68ae40ecb627a2d2cd3/py_partiql_parser-0.6.3-py2.py3-none-any.whl", hash = "sha256:deb0769c3346179d2f590dcbde556f708cdb929059fb654bad75f4cf6e07f582", size = 23752, upload-time = "2025-10-18T13:56:12.256Z" },
]
[[package]]
name = "py-serializable"
version = "2.1.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "defusedxml" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z" },
]
[[package]]
name = "py-spy"
version = "0.4.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/93/d8/5b71371f50cf153b1307e5a11ac8a4ce4d85651dae946bd7e9a064146545/py_spy-0.4.2.tar.gz", hash = "sha256:90e600b27bb6bb40479637baca5a5b4bc2ba3395c93d889e672315d93042c4ae", size = 286374, upload-time = "2026-04-24T22:08:54.906Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/d8/5b71371f50cf153b1307e5a11ac8a4ce4d85651dae946bd7e9a064146545/py_spy-0.4.2.tar.gz", hash = "sha256:90e600b27bb6bb40479637baca5a5b4bc2ba3395c93d889e672315d93042c4ae", size = 286374, upload-time = "2026-04-24T22:08:54.906Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ef/21/ec030145a0c7992bd4b9eafb2f06f56358b3a5339eab4a16534baf3c69aa/py_spy-0.4.2-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1ccf688393105111684435f035bc14ec3f22117dd2b85b2414612cf27a22755a", size = 3743992, upload-time = "2026-04-24T22:08:45.438Z" },
- { url = "https://files.pythonhosted.org/packages/50/80/de5fd27243c2be03692ecd317bf0dbe24b4c6f78f689ce111e7277a7cb09/py_spy-0.4.2-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:a0e6f6810ccf0fc5e64e85e0182a5b626c4496eec01b14fb8755154b363a4831", size = 1859057, upload-time = "2026-04-24T22:08:46.946Z" },
- { url = "https://files.pythonhosted.org/packages/89/23/3eb4c23c684ebd667674ce1d076ae855e0621d1d9bd5e052aa3f7982f757/py_spy-0.4.2-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:142887e984a4e541071c99a4401ff8c3770f255d329dbd0f64e8c1dd51882cce", size = 2828136, upload-time = "2026-04-24T22:08:48.519Z" },
- { url = "https://files.pythonhosted.org/packages/ca/01/6314152cf9ad3310ebacbf2c47b5ed858086530f8e12b1a665725ca5e0f4/py_spy-0.4.2-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1c6d9b0e2379ead5bf792df43f4cf36153aa79e6dda4fb8ac7740cf8017110", size = 2857707, upload-time = "2026-04-24T22:08:49.677Z" },
- { url = "https://files.pythonhosted.org/packages/cc/1f/0960a129d504728d28a51dbd5a04ce94031eb75bac676341da7aefdd8232/py_spy-0.4.2-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24720573f95230653b457671a1dcc3c5a381fcf4e92677761e328a430ad251b2", size = 2301852, upload-time = "2026-04-24T22:08:51.152Z" },
- { url = "https://files.pythonhosted.org/packages/f9/34/dd7d3c763a00b7b965e25a5eab0acd1a345dbaf0f45fffe595278873a1c0/py_spy-0.4.2-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aeb0323409199c785f730645e9f4bb7a7b9ca2c481f2c331a55642b5d13fa52f", size = 2936518, upload-time = "2026-04-24T22:08:52.264Z" },
- { url = "https://files.pythonhosted.org/packages/6f/ed/1409cdb557e558a6c98003ab12fdd4284699e158c167c187cb0f124eea4c/py_spy-0.4.2-py2.py3-none-win_amd64.whl", hash = "sha256:8b06a353c177677e4e1701b288d8c58e2f8d4208ee81a8048d9f72ba800918f8", size = 1894002, upload-time = "2026-04-24T22:08:53.811Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/21/ec030145a0c7992bd4b9eafb2f06f56358b3a5339eab4a16534baf3c69aa/py_spy-0.4.2-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1ccf688393105111684435f035bc14ec3f22117dd2b85b2414612cf27a22755a", size = 3743992, upload-time = "2026-04-24T22:08:45.438Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/50/80/de5fd27243c2be03692ecd317bf0dbe24b4c6f78f689ce111e7277a7cb09/py_spy-0.4.2-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:a0e6f6810ccf0fc5e64e85e0182a5b626c4496eec01b14fb8755154b363a4831", size = 1859057, upload-time = "2026-04-24T22:08:46.946Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/89/23/3eb4c23c684ebd667674ce1d076ae855e0621d1d9bd5e052aa3f7982f757/py_spy-0.4.2-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:142887e984a4e541071c99a4401ff8c3770f255d329dbd0f64e8c1dd51882cce", size = 2828136, upload-time = "2026-04-24T22:08:48.519Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ca/01/6314152cf9ad3310ebacbf2c47b5ed858086530f8e12b1a665725ca5e0f4/py_spy-0.4.2-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1c6d9b0e2379ead5bf792df43f4cf36153aa79e6dda4fb8ac7740cf8017110", size = 2857707, upload-time = "2026-04-24T22:08:49.677Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cc/1f/0960a129d504728d28a51dbd5a04ce94031eb75bac676341da7aefdd8232/py_spy-0.4.2-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24720573f95230653b457671a1dcc3c5a381fcf4e92677761e328a430ad251b2", size = 2301852, upload-time = "2026-04-24T22:08:51.152Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f9/34/dd7d3c763a00b7b965e25a5eab0acd1a345dbaf0f45fffe595278873a1c0/py_spy-0.4.2-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aeb0323409199c785f730645e9f4bb7a7b9ca2c481f2c331a55642b5d13fa52f", size = 2936518, upload-time = "2026-04-24T22:08:52.264Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/ed/1409cdb557e558a6c98003ab12fdd4284699e158c167c187cb0f124eea4c/py_spy-0.4.2-py2.py3-none-win_amd64.whl", hash = "sha256:8b06a353c177677e4e1701b288d8c58e2f8d4208ee81a8048d9f72ba800918f8", size = 1894002, upload-time = "2026-04-24T22:08:53.811Z" },
]
[[package]]
name = "pycparser"
version = "3.0"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
[[package]]
name = "pydantic"
version = "2.13.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
]
[[package]]
name = "pydantic-core"
version = "2.46.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" },
- { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" },
- { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" },
- { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" },
- { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" },
- { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" },
- { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" },
- { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" },
- { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" },
- { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" },
- { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" },
- { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" },
- { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" },
- { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" },
- { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
- { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
- { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
- { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
- { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
- { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
- { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
- { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
- { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
- { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
- { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
- { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
- { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
- { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
- { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
- { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
- { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
- { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
- { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
- { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
- { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
- { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
- { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
- { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
- { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
- { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
- { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
- { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
- { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
- { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
- { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
- { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
- { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
- { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
- { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
- { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
- { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
- { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
- { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
- { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
- { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
- { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
- { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
- { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
- { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
- { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
- { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
- { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
- { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
- { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
- { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
- { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
- { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
- { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
- { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
- { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
- { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
- { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
- { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
- { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
- { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
- { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
- { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
- { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
- { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
- { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
- { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
- { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
- { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
- { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
- { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
- { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
- { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
- { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
- { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
- { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
- { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
- { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
- { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
- { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
- { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
- { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
- { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
- { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
- { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
- { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
- { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
- { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
- { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
- { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
- { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
]
[[package]]
name = "pydantic-settings"
version = "2.14.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.13.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
+]
+
+[package.optional-dependencies]
+crypto = [
+ { name = "cryptography" },
]
[[package]]
name = "pymdown-extensions"
version = "10.21.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "markdown" },
{ name = "pyyaml" },
]
-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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "pyparsing"
version = "3.3.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "pyright"
version = "1.1.410"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/33/288b5868fa00846dacf249633719d747893e54aebd196b9968ac1878a5d3/pyright-1.1.410-py3-none-any.whl", hash = "sha256:5e961bed37cacf96b3f7cd7b1da39b350a9239aa2e69138d0e88f728cfaf296c", size = 6082448, upload-time = "2026-06-01T17:35:46.387Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/33/288b5868fa00846dacf249633719d747893e54aebd196b9968ac1878a5d3/pyright-1.1.410-py3-none-any.whl", hash = "sha256:5e961bed37cacf96b3f7cd7b1da39b350a9239aa2e69138d0e88f728cfaf296c", size = 6082448, upload-time = "2026-06-01T17:35:46.387Z" },
]
[[package]]
name = "pytest"
version = "9.1.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
@@ -3416,179 +3648,189 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "pytest-asyncio"
version = "1.4.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "pytest-benchmark"
version = "5.2.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "py-cpuinfo" },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" },
]
[[package]]
name = "pytest-cov"
version = "7.1.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "coverage", extra = ["toml"] },
{ name = "pluggy" },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
]
[[package]]
name = "pytest-env"
-version = "1.1.5"
-source = { registry = "https://pypi.org/simple" }
+version = "1.6.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pytest" },
+ { name = "python-dotenv" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1f/31/27f28431a16b83cab7a636dce59cf397517807d247caa38ee67d65e71ef8/pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf", size = 8911, upload-time = "2024-09-17T22:39:18.566Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ff/69/4db1c30625af0621df8dbe73797b38b6d1b04e15d021dd5d26a6d297f78c/pytest_env-1.6.0.tar.gz", hash = "sha256:ac02d6fba16af54d61e311dd70a3c61024a4e966881ea844affc3c8f0bf207d3", size = 16163, upload-time = "2026-03-12T22:39:43.78Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/de/b8/87cfb16045c9d4092cfcf526135d73b88101aac83bc1adcf82dfb5fd3833/pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30", size = 6141, upload-time = "2024-09-17T22:39:16.942Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/16/ad52f56b96d851a2bcfdc1e754c3531341885bd7177a128c13ff2ca72ab4/pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3", size = 10400, upload-time = "2026-03-12T22:39:41.887Z" },
]
[[package]]
name = "pytest-html"
version = "4.2.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "jinja2" },
{ name = "pytest" },
{ name = "pytest-metadata" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c4/08/2076aa09507e51c1119d16a84c6307354d16270558f1a44fc9a2c99fdf1d/pytest_html-4.2.0.tar.gz", hash = "sha256:b6a88cba507500d8709959201e2e757d3941e859fd17cfd4ed87b16fc0c67912", size = 108634, upload-time = "2026-01-19T11:25:26.471Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c4/08/2076aa09507e51c1119d16a84c6307354d16270558f1a44fc9a2c99fdf1d/pytest_html-4.2.0.tar.gz", hash = "sha256:b6a88cba507500d8709959201e2e757d3941e859fd17cfd4ed87b16fc0c67912", size = 108634, upload-time = "2026-01-19T11:25:26.471Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/84/47/07046e0acedc12fe2bae79cf6c73ad67f51ae9d67df64d06b0f3eac73d36/pytest_html-4.2.0-py3-none-any.whl", hash = "sha256:ff5caf3e17a974008e5816edda61168e6c3da442b078a44f8744865862a85636", size = 23801, upload-time = "2026-01-19T11:25:25.008Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/84/47/07046e0acedc12fe2bae79cf6c73ad67f51ae9d67df64d06b0f3eac73d36/pytest_html-4.2.0-py3-none-any.whl", hash = "sha256:ff5caf3e17a974008e5816edda61168e6c3da442b078a44f8744865862a85636", size = 23801, upload-time = "2026-01-19T11:25:25.008Z" },
]
[[package]]
name = "pytest-metadata"
version = "3.1.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" },
]
[[package]]
name = "pytest-mock"
version = "3.15.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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-timeout"
version = "2.4.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" },
]
[[package]]
name = "pytest-xdist"
version = "3.8.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "six" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "python-discovery"
version = "1.4.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "filelock" },
{ name = "platformdirs" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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-gitlab"
version = "6.5.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "requests" },
{ name = "requests-toolbelt" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9a/bd/b30f1d3b303cb5d3c72e2d57a847d699e8573cbdfd67ece5f1795e49da1c/python_gitlab-6.5.0.tar.gz", hash = "sha256:97553652d94b02de343e9ca92782239aa2b5f6594c5482331a9490d9d5e8737d", size = 400591, upload-time = "2025-10-17T21:40:02.89Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9a/bd/b30f1d3b303cb5d3c72e2d57a847d699e8573cbdfd67ece5f1795e49da1c/python_gitlab-6.5.0.tar.gz", hash = "sha256:97553652d94b02de343e9ca92782239aa2b5f6594c5482331a9490d9d5e8737d", size = 400591, upload-time = "2025-10-17T21:40:02.89Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/34/bd/b0d440685fbcafee462bed793a74aea88541887c4c30556a55ac64914b8d/python_gitlab-6.5.0-py3-none-any.whl", hash = "sha256:494e1e8e5edd15286eaf7c286f3a06652688f1ee20a49e2a0218ddc5cc475e32", size = 144419, upload-time = "2025-10-17T21:40:01.233Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/34/bd/b0d440685fbcafee462bed793a74aea88541887c4c30556a55ac64914b8d/python_gitlab-6.5.0-py3-none-any.whl", hash = "sha256:494e1e8e5edd15286eaf7c286f3a06652688f1ee20a49e2a0218ddc5cc475e32", size = 144419, upload-time = "2025-10-17T21:40:01.233Z" },
+]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.32"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
]
[[package]]
name = "python-semantic-release"
version = "10.5.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "click" },
{ name = "click-option-group" },
@@ -3604,769 +3846,775 @@ dependencies = [
{ name = "shellingham" },
{ name = "tomlkit" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/19/3a/7332b822825ed0e902c6e950e0d1e90e8f666fd12eb27855d1c8b6677eff/python_semantic_release-10.5.3.tar.gz", hash = "sha256:de4da78635fa666e5774caaca2be32063cae72431eb75e2ac23b9f2dfd190785", size = 618034, upload-time = "2025-12-14T22:37:29.782Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/3a/7332b822825ed0e902c6e950e0d1e90e8f666fd12eb27855d1c8b6677eff/python_semantic_release-10.5.3.tar.gz", hash = "sha256:de4da78635fa666e5774caaca2be32063cae72431eb75e2ac23b9f2dfd190785", size = 618034, upload-time = "2025-12-14T22:37:29.782Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6b/01/ada29a1215df601bded0a2efd3b6d53864a0a9e0a9ea52aeaebe14fd03fd/python_semantic_release-10.5.3-py3-none-any.whl", hash = "sha256:1be0e07c36fa1f1ec9da4f438c1f6bbd7bc10eb0d6ac0089b0643103708c2823", size = 152716, upload-time = "2025-12-14T22:37:28.089Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6b/01/ada29a1215df601bded0a2efd3b6d53864a0a9e0a9ea52aeaebe14fd03fd/python_semantic_release-10.5.3-py3-none-any.whl", hash = "sha256:1be0e07c36fa1f1ec9da4f438c1f6bbd7bc10eb0d6ac0089b0643103708c2823", size = 152716, upload-time = "2025-12-14T22:37:28.089Z" },
]
[[package]]
name = "pywin32"
-version = "312"
-source = { registry = "https://pypi.org/simple" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/fe/1b/9cfdeac80ee45bebbbcb31f1b7b99a0d81a1c72de48d837be984e0e88b1d/pywin32-312-cp310-cp310-win32.whl", hash = "sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e", size = 6361387, upload-time = "2026-06-04T07:49:14.329Z" },
- { url = "https://files.pythonhosted.org/packages/33/b1/7afc96d041d982c27bc2df6f853d43f01fd273e3d39d04be3647ddeb533d/pywin32-312-cp310-cp310-win_amd64.whl", hash = "sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db", size = 6926780, upload-time = "2026-06-04T07:49:16.881Z" },
- { url = "https://files.pythonhosted.org/packages/ce/3a/4140da9ad54108e517f4a16b2d83da3033e08662144623e1239587cb7db6/pywin32-312-cp310-cp310-win_arm64.whl", hash = "sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd", size = 4307203, upload-time = "2026-06-04T07:49:18.993Z" },
- { url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659, upload-time = "2026-06-04T07:49:21.349Z" },
- { url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825, upload-time = "2026-06-04T07:49:23.934Z" },
- { url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875, upload-time = "2026-06-04T07:49:26.416Z" },
- { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" },
- { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" },
- { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" },
- { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" },
- { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" },
- { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" },
- { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" },
- { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" },
- { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" },
- { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" },
- { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" },
- { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" },
+version = "311"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
- { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
- { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
- { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
- { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
- { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
- { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
- { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
- { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
- { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
- { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
- { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
- { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
- { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
- { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
- { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
- { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
- { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
- { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
- { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
- { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
- { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
- { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
- { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
- { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
- { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
- { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
- { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
- { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
- { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
- { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
- { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
- { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
- { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
- { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
- { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
- { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
- { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
- { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
- { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
- { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
- { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
- { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
- { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
- { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
- { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
- { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
- { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
- { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
- { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
- { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
- { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
- { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
- { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
- { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
- { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "pyyaml-env-tag"
version = "1.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pyyaml" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "radon"
version = "6.0.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama" },
{ name = "mando" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b1/6d/98e61600febf6bd929cf04154537c39dc577ce414bafbfc24a286c4fa76d/radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5", size = 1874992, upload-time = "2023-03-26T06:24:38.868Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/6d/98e61600febf6bd929cf04154537c39dc577ce414bafbfc24a286c4fa76d/radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5", size = 1874992, upload-time = "2023-03-26T06:24:38.868Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859", size = 52784, upload-time = "2023-03-26T06:24:33.949Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859", size = 52784, upload-time = "2023-03-26T06:24:33.949Z" },
]
[[package]]
name = "readme-renderer"
version = "45.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "docutils" },
{ name = "nh3" },
{ name = "pygments" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/02/51/d3a6ea424652c60f05600d8c2e01a55c913755e7cdad64afabbd1aa16f44/readme_renderer-45.0.tar.gz", hash = "sha256:030a8fac74904f8fba11ad1bb6964e3f76e896dc7e5e71f16af190c9056696d1", size = 36172, upload-time = "2026-06-09T21:05:17.37Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/51/d3a6ea424652c60f05600d8c2e01a55c913755e7cdad64afabbd1aa16f44/readme_renderer-45.0.tar.gz", hash = "sha256:030a8fac74904f8fba11ad1bb6964e3f76e896dc7e5e71f16af190c9056696d1", size = 36172, upload-time = "2026-06-09T21:05:17.37Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/97/1b/295bf2fa3e740131778065e5ffa2c481f0e7210182d408e9a2c244ff5b0c/readme_renderer-45.0-py3-none-any.whl", hash = "sha256:3385ed220117104a2bceb4a9dac8c5fdf6d1f96890d7ea2a9c7174fd5c84091f", size = 14134, upload-time = "2026-06-09T21:05:15.85Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/97/1b/295bf2fa3e740131778065e5ffa2c481f0e7210182d408e9a2c244ff5b0c/readme_renderer-45.0-py3-none-any.whl", hash = "sha256:3385ed220117104a2bceb4a9dac8c5fdf6d1f96890d7ea2a9c7174fd5c84091f", size = 14134, upload-time = "2026-06-09T21:05:15.85Z" },
]
[[package]]
name = "referencing"
version = "0.37.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "attrs" },
- { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "rpds-py", version = "0.30.0", source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }, marker = "python_full_version < '3.11'" },
+ { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }, marker = "python_full_version >= '3.11'" },
{ 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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
-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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "requests-mock"
version = "1.12.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "requests" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/92/32/587625f91f9a0a3d84688bf9cfc4b2480a7e8ec327cefd0ff2ac891fd2cf/requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401", size = 60901, upload-time = "2024-03-29T03:54:29.446Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/92/32/587625f91f9a0a3d84688bf9cfc4b2480a7e8ec327cefd0ff2ac891fd2cf/requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401", size = 60901, upload-time = "2024-03-29T03:54:29.446Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/97/ec/889fbc557727da0c34a33850950310240f2040f3b1955175fdb2b36a8910/requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", size = 27695, upload-time = "2024-03-29T03:54:27.64Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/97/ec/889fbc557727da0c34a33850950310240f2040f3b1955175fdb2b36a8910/requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", size = 27695, upload-time = "2024-03-29T03:54:27.64Z" },
]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "requests" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
]
[[package]]
name = "responses"
version = "0.26.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "pyyaml" },
{ name = "requests" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c2/58/1fb6de3503428196df78638f991ec8095274f1ee9723e272ee4d9ff0092b/responses-0.26.1.tar.gz", hash = "sha256:2eb3218553cc8f79b57d257bac23af5e1bf381f5b9390b1767816f0843e01dc2", size = 83088, upload-time = "2026-05-21T19:56:39.747Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/58/1fb6de3503428196df78638f991ec8095274f1ee9723e272ee4d9ff0092b/responses-0.26.1.tar.gz", hash = "sha256:2eb3218553cc8f79b57d257bac23af5e1bf381f5b9390b1767816f0843e01dc2", size = 83088, upload-time = "2026-05-21T19:56:39.747Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3a/31/6a620b4427d546b9e7cca8b3b8c5f0559d9cef2bb9eedcda7f73c1473c19/responses-0.26.1-py3-none-any.whl", hash = "sha256:8aacc4586eb08fb2208ef64a9eb4258d9b0c6e6f4260845f2f018ab847495345", size = 35502, upload-time = "2026-05-21T19:56:38.046Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3a/31/6a620b4427d546b9e7cca8b3b8c5f0559d9cef2bb9eedcda7f73c1473c19/responses-0.26.1-py3-none-any.whl", hash = "sha256:8aacc4586eb08fb2208ef64a9eb4258d9b0c6e6f4260845f2f018ab847495345", size = 35502, upload-time = "2026-05-21T19:56:38.046Z" },
]
[[package]]
name = "rfc3339-validator"
version = "0.1.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "six" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
]
[[package]]
name = "rfc3986"
version = "2.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" },
]
[[package]]
-name = "rfc3987"
-version = "1.3.8"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/14/bb/f1395c4b62f251a1cb503ff884500ebd248eed593f41b469f89caa3547bd/rfc3987-1.3.8.tar.gz", hash = "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733", size = 20700, upload-time = "2018-07-29T17:23:47.954Z" }
+name = "rfc3986-validator"
+version = "0.1.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/65/d4/f7407c3d15d5ac779c3dd34fbbc6ea2090f77bd7dd12f207ccf881551208/rfc3987-1.3.8-py2.py3-none-any.whl", hash = "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53", size = 13377, upload-time = "2018-07-29T17:23:45.313Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" },
+]
+
+[[package]]
+name = "rfc3987-syntax"
+version = "1.1.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "lark" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" },
]
[[package]]
name = "rich"
version = "14.3.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" },
]
[[package]]
name = "rich-argparse"
version = "1.8.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "rich" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6a/e5/1064c43203a357d668cd42435f7a15fe6af51512d85b2104fecb937aa861/rich_argparse-1.8.0.tar.gz", hash = "sha256:679df3d832fa94ad6e4bdb07ded088cd7ea2dddc58ae9b2b46346a40b06cbc0c", size = 38940, upload-time = "2026-05-01T15:18:43.604Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/e5/1064c43203a357d668cd42435f7a15fe6af51512d85b2104fecb937aa861/rich_argparse-1.8.0.tar.gz", hash = "sha256:679df3d832fa94ad6e4bdb07ded088cd7ea2dddc58ae9b2b46346a40b06cbc0c", size = 38940, upload-time = "2026-05-01T15:18:43.604Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0b/35/1cceccc5fcb50fa2ed53e2aa278cd032f3902682a73e763fb1ac3be8e6fa/rich_argparse-1.8.0-py3-none-any.whl", hash = "sha256:d2a3ce7854654e2253c578763ab0a32f05016f23a55fadba7b9a91b6c0e92142", size = 25616, upload-time = "2026-05-01T15:18:42.395Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0b/35/1cceccc5fcb50fa2ed53e2aa278cd032f3902682a73e763fb1ac3be8e6fa/rich_argparse-1.8.0-py3-none-any.whl", hash = "sha256:d2a3ce7854654e2253c578763ab0a32f05016f23a55fadba7b9a91b6c0e92142", size = 25616, upload-time = "2026-05-01T15:18:42.395Z" },
]
[[package]]
name = "rpds-py"
version = "0.30.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
resolution-markers = [
"python_full_version < '3.11'",
]
-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" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "rpds-py"
version = "2026.5.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
resolution-markers = [
"python_full_version >= '3.12'",
"python_full_version == '3.11.*'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" },
- { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" },
- { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" },
- { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" },
- { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" },
- { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" },
- { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" },
- { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" },
- { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" },
- { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" },
- { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" },
- { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" },
- { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" },
- { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" },
- { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" },
- { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" },
- { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" },
- { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" },
- { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" },
- { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" },
- { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" },
- { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" },
- { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" },
- { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" },
- { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" },
- { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" },
- { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" },
- { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" },
- { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" },
- { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" },
- { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" },
- { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" },
- { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" },
- { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" },
- { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" },
- { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" },
- { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" },
- { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" },
- { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" },
- { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" },
- { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" },
- { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" },
- { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" },
- { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" },
- { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" },
- { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" },
- { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" },
- { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" },
- { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" },
- { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" },
- { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" },
- { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" },
- { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" },
- { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" },
- { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" },
- { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" },
- { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" },
- { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" },
- { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" },
- { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" },
- { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" },
- { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" },
- { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" },
- { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" },
- { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" },
- { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" },
- { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" },
- { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" },
- { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" },
- { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" },
- { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" },
- { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" },
- { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" },
- { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" },
- { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" },
- { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" },
- { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" },
- { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" },
- { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" },
- { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" },
- { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" },
- { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" },
- { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" },
- { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" },
- { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" },
- { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" },
- { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" },
- { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" },
- { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" },
- { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" },
- { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" },
- { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" },
- { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" },
- { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" },
- { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" },
- { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" },
- { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" },
- { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" },
- { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" },
- { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" },
- { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" },
- { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" },
- { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" },
- { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" },
- { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" },
- { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" },
- { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" },
- { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" },
- { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" },
- { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" },
- { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" },
- { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" },
- { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" },
- { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" },
- { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" },
- { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" },
- { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" },
- { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" },
- { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" },
- { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" },
- { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" },
- { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" },
- { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" },
- { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" },
- { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" },
- { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" },
- { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" },
- { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" },
- { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" },
]
[[package]]
name = "ruamel-yaml"
-version = "0.17.40"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/d1/d6/eb2833ccba5ea36f8f4de4bcfa0d1a91eb618f832d430b70e3086821f251/ruamel.yaml-0.17.40.tar.gz", hash = "sha256:6024b986f06765d482b5b07e086cc4b4cd05dd22ddcbc758fa23d54873cf313d", size = 137672, upload-time = "2023-10-20T12:53:56.073Z" }
+version = "0.19.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/35/79/5e2cffa1c77432f11cd93a5351f30732c997a239d3a3090856a72d6d8ba7/ruamel.yaml-0.17.40-py3-none-any.whl", hash = "sha256:b16b6c3816dff0a93dca12acf5e70afd089fa5acb80604afd1ffa8b465b7722c", size = 113666, upload-time = "2023-10-20T12:53:52.628Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" },
]
[[package]]
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" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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.18"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" },
- { url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" },
- { url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" },
- { url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" },
- { url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" },
- { url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" },
- { url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" },
- { url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" },
- { url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" },
- { url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" },
- { url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" },
- { url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" },
- { url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" },
- { url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" },
- { url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" },
- { url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" },
- { url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" },
]
[[package]]
name = "s3transfer"
version = "0.19.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "botocore" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" },
]
[[package]]
name = "safety"
version = "3.8.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "authlib" },
{ name = "certifi" },
@@ -4388,15 +4636,15 @@ dependencies = [
{ name = "typer" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c2/7b/8e1d580c5178f0736b806b7199827e61e2a2569eec5b49ec75da6273bbdf/safety-3.8.1.tar.gz", hash = "sha256:e646123b976bbb6707cfaacae8c926e2f886b744a60e0f410e8610a3a4eaf7be", size = 412947, upload-time = "2026-05-29T15:09:33.355Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/7b/8e1d580c5178f0736b806b7199827e61e2a2569eec5b49ec75da6273bbdf/safety-3.8.1.tar.gz", hash = "sha256:e646123b976bbb6707cfaacae8c926e2f886b744a60e0f410e8610a3a4eaf7be", size = 412947, upload-time = "2026-05-29T15:09:33.355Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4b/f5/498db84333a644835e572c0d96cfa705bf9871f863289a7845082d54755e/safety-3.8.1-py3-none-any.whl", hash = "sha256:953c1c3c60c873f53a6cc250b2a9c4b38bb6ef45f0625990e43f20bff916c965", size = 340521, upload-time = "2026-05-29T15:09:31.622Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4b/f5/498db84333a644835e572c0d96cfa705bf9871f863289a7845082d54755e/safety-3.8.1-py3-none-any.whl", hash = "sha256:953c1c3c60c873f53a6cc250b2a9c4b38bb6ef45f0625990e43f20bff916c965", size = 340521, upload-time = "2026-05-29T15:09:31.622Z" },
]
[[package]]
name = "safety-schemas"
version = "0.0.16"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "dparse" },
{ name = "packaging" },
@@ -4404,296 +4652,375 @@ dependencies = [
{ name = "ruamel-yaml" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c2/ef/0e07dfdb4104c4e42ae9fc6e8a0da7be2d72ac2ee198b32f7500796de8f3/safety_schemas-0.0.16.tar.gz", hash = "sha256:3bb04d11bd4b5cc79f9fa183c658a6a8cf827a9ceec443a5ffa6eed38a50a24e", size = 54815, upload-time = "2025-09-16T14:35:31.973Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c2/ef/0e07dfdb4104c4e42ae9fc6e8a0da7be2d72ac2ee198b32f7500796de8f3/safety_schemas-0.0.16.tar.gz", hash = "sha256:3bb04d11bd4b5cc79f9fa183c658a6a8cf827a9ceec443a5ffa6eed38a50a24e", size = 54815, upload-time = "2025-09-16T14:35:31.973Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/84/a2/7840cc32890ce4b84668d3d9dfe15a48355b683ae3fb627ac97ac5a4265f/safety_schemas-0.0.16-py3-none-any.whl", hash = "sha256:6760515d3fd1e6535b251cd73014bd431d12fe0bfb8b6e8880a9379b5ab7aa44", size = 39292, upload-time = "2025-09-16T14:35:32.84Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/84/a2/7840cc32890ce4b84668d3d9dfe15a48355b683ae3fb627ac97ac5a4265f/safety_schemas-0.0.16-py3-none-any.whl", hash = "sha256:6760515d3fd1e6535b251cd73014bd431d12fe0bfb8b6e8880a9379b5ab7aa44", size = 39292, upload-time = "2025-09-16T14:35:32.84Z" },
]
[[package]]
name = "sarif-om"
version = "1.0.4"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "attrs" },
{ name = "pbr" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ba/de/bbdd93fe456d4011500784657c5e4a31e3f4fcbb276255d4db1213aed78c/sarif_om-1.0.4.tar.gz", hash = "sha256:cd5f416b3083e00d402a92e449a7ff67af46f11241073eea0461802a3b5aef98", size = 28847, upload-time = "2019-10-05T20:11:23.338Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/de/bbdd93fe456d4011500784657c5e4a31e3f4fcbb276255d4db1213aed78c/sarif_om-1.0.4.tar.gz", hash = "sha256:cd5f416b3083e00d402a92e449a7ff67af46f11241073eea0461802a3b5aef98", size = 28847, upload-time = "2019-10-05T20:11:23.338Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/82/7c/1d3d0467565aa8b3e77ab8712042a09dd1158056826f45783f3d2b34adf1/sarif_om-1.0.4-py3-none-any.whl", hash = "sha256:539ef47a662329b1c8502388ad92457425e95dc0aaaf995fe46f4984c4771911", size = 30193, upload-time = "2019-10-05T20:11:21.577Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/82/7c/1d3d0467565aa8b3e77ab8712042a09dd1158056826f45783f3d2b34adf1/sarif_om-1.0.4-py3-none-any.whl", hash = "sha256:539ef47a662329b1c8502388ad92457425e95dc0aaaf995fe46f4984c4771911", size = 30193, upload-time = "2019-10-05T20:11:21.577Z" },
]
[[package]]
name = "secretstorage"
version = "3.5.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "semantic-version"
+version = "2.10.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "semgrep"
-version = "1.79.0"
-source = { registry = "https://pypi.org/simple" }
+version = "1.167.0"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "attrs" },
{ name = "boltons" },
{ name = "click" },
{ name = "click-option-group" },
{ name = "colorama" },
- { name = "defusedxml" },
{ 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/45/61/9ee9e601ddc9f9073708d4e6886d0c7021b59b3180b6cb53c0bd01b393d9/semgrep-1.79.0.tar.gz", hash = "sha256:fde15d090b4beb865e12c2c727404c8dee2f41b9d793a7f972b278cdefb22bea", size = 27421587, upload-time = "2024-07-10T10:06:05.122Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/33/cb/5c694b392583aa73727272ae48aecf28703ba98d4aa1dd96f7bf2ac2b0d6/semgrep-1.167.0.tar.gz", hash = "sha256:7ee779321c3a8580ba1192d4933422ae29f8802ca38edcbfa942ce0a97592ac1", size = 56149438, upload-time = "2026-06-17T18:23:54.505Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/56/4a/469abc30b134354632d8b8e83249121447fca74a615509a09bed90340813/semgrep-1.79.0-cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-any.whl", hash = "sha256:5a28858c1f5249bf4fed3f180f2db2589ce1832c1058eb6d18a0f086c91dadc1", size = 27832465, upload-time = "2024-07-10T10:05:45.254Z" },
- { url = "https://files.pythonhosted.org/packages/e9/23/eff37582f900cf742b5fc7709dfd0e63cc2bc0faefe6ec200f150666fa7a/semgrep-1.79.0-cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-macosx_10_14_x86_64.whl", hash = "sha256:4b22b5f4db17204648baf8bc58fcd74c09eb5f41bd407422193253b4de9af18e", size = 28050871, upload-time = "2024-07-10T10:05:52.086Z" },
- { url = "https://files.pythonhosted.org/packages/d6/88/35615a4e1142755cb3d2c86d63160f63ab770c111c82de9318bba27fb888/semgrep-1.79.0-cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-macosx_11_0_arm64.whl", hash = "sha256:fe5cf0ac8afdb786cbd4e6c97c418ca7adc8f4c42584a69dc7c10e15db5f7d9b", size = 33805877, upload-time = "2024-07-10T10:05:56.42Z" },
- { url = "https://files.pythonhosted.org/packages/fd/84/3b6afc829f54b331f47d55c565096ef74a98d96d794612d2e5330062d373/semgrep-1.79.0-cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-musllinux_1_0_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41797931371d05c41a6e09861b3d631136b4fe644d80802b2fd48af650e5fa5a", size = 32479030, upload-time = "2024-07-10T10:06:00.777Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/f8/93423330211b987ad6ce17d9fb54cb3772a02c28fb2bb9e302f7be5164b1/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:7d8b222f13a947c164e6b89e64258474c0fd59c08bfd1071ed7b3216afcd8cf6", size = 45184663, upload-time = "2026-06-17T18:23:24.528Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/08/0b/ac9ff80fa5919f5048fb0005ff7c578f6d0543ee5080cdab564a625d919d/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:ca5ba16bd2bf7a3e305a32ad292f66246947d31c41603b580453acd7ab859827", size = 49140453, upload-time = "2026-06-17T18:23:28.554Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/08/de/e72a0e28174cb34a4fbe0d9d5bab805d16683dae6c3eb71bdf06f3f0f15a/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_aarch64.whl", hash = "sha256:a4912d2af1371ad96524163ec10842227fcc79dd14fd8b6a706d598bf380fcbc", size = 71142408, upload-time = "2026-06-17T18:23:32.685Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/0a/f287be6aff5a2d10528df9e985f8a630fc552960b4e966a21e3edb7d705c/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_x86_64.whl", hash = "sha256:687f08c3c3951bcc6f67d63cc0019c40401c900ecc26a6e56382b6cc02f4f50f", size = 69012140, upload-time = "2026-06-17T18:23:37.386Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/bd/95b42a821ff4eb15887716591cd472afb7e3003761d3b43ad6a8e833fd6d/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_aarch64.whl", hash = "sha256:e7d82cc6b5eb8bd699b2a329e6c11de7ba039643c69c1b61150cda1298585aee", size = 79105757, upload-time = "2026-06-17T18:23:41.883Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/c7/d3affb19e16180b034363539f06a274b94df58b10940c6c0c97bc8b90eeb/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_x86_64.whl", hash = "sha256:912be28e47409bb1c53d1a4512f2a39ac9e693c7895cb94d8c181702c8bed837", size = 76605570, upload-time = "2026-06-17T18:23:46.376Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/38/7e/203e7dbea7c1f534eedce440216c7c77151a1dab96c4b02b16edff5b3337/semgrep-1.167.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:b01823d9b540cccb92e09a404ee796f5fe2a28c132170967386909e3b77f5b8e", size = 57083314, upload-time = "2026-06-17T18:23:50.727Z" },
]
[[package]]
name = "semver"
version = "3.0.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
]
[[package]]
name = "setuptools"
version = "82.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
name = "six"
version = "1.17.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "smmap"
version = "5.0.3"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "sqlalchemy"
version = "2.0.51"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" },
- { url = "https://files.pythonhosted.org/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" },
- { url = "https://files.pythonhosted.org/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" },
- { url = "https://files.pythonhosted.org/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" },
- { url = "https://files.pythonhosted.org/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" },
- { url = "https://files.pythonhosted.org/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" },
- { url = "https://files.pythonhosted.org/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" },
- { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" },
- { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" },
- { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" },
- { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" },
- { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" },
- { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" },
- { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" },
- { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" },
- { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" },
- { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" },
- { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" },
- { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" },
- { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" },
- { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" },
- { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" },
- { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" },
- { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" },
- { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" },
- { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" },
- { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" },
- { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" },
- { url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" },
- { url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" },
- { url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" },
- { url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" },
- { url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" },
- { url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" },
- { url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" },
- { url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" },
- { url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" },
- { url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" },
- { url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" },
- { url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" },
- { url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" },
- { url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" },
- { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" },
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" },
+]
+
+[[package]]
+name = "sse-starlette"
+version = "3.4.5"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+dependencies = [
+ { name = "anyio" },
+ { name = "starlette" },
+]
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d2/1b/bc9e3e7a72dcdad7dc7888758f5d00f56f8909ed5cfdff822bd72bb4c520/sse_starlette-3.4.5.tar.gz", hash = "sha256:83072538bc211a2f68b7b0422226c4af3e9b62e106e07034664b832ca019842a", size = 35249, upload-time = "2026-06-20T17:36:58.322Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/78/75/c88d3f5dafd59c791da1ce27650d30bf5b70cbf1cbf01cd00e5f9e360915/sse_starlette-3.4.5-py3-none-any.whl", hash = "sha256:e71bad53323f65573c3864a6c3bd0c1eb6e5f092b2e48082b0c35927d19ca296", size = 16518, upload-time = "2026-06-20T17:36:56.729Z" },
]
[[package]]
name = "stack-data"
version = "0.6.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "asttokens" },
{ name = "executing" },
{ name = "pure-eval" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
]
[[package]]
name = "starlette"
version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" },
]
[[package]]
name = "stevedore"
version = "5.8.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" },
]
[[package]]
name = "sympy"
version = "1.14.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "mpmath" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
]
[[package]]
name = "tenacity"
version = "9.1.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
-]
-
-[[package]]
-name = "toml"
-version = "0.10.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
]
[[package]]
name = "tomli"
-version = "2.0.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/35/b9/de2a5c0144d7d75a57ff355c0c24054f965b2dc3036456ae03a51ea6264b/tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed", size = 16096, upload-time = "2024-10-02T10:46:13.208Z" }
+version = "2.4.1"
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237, upload-time = "2024-10-02T10:46:11.806Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.13.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
]
[[package]]
name = "tqdm"
version = "4.68.3"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" },
]
[[package]]
name = "traitlets"
version = "5.15.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" },
]
[[package]]
name = "truststore"
version = "0.10.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]
[[package]]
name = "twine"
version = "6.2.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "id" },
{ name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" },
@@ -4705,119 +5032,119 @@ dependencies = [
{ name = "rich" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" },
]
[[package]]
name = "typer"
version = "0.23.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "annotated-doc" },
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
]
-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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/d5/91/9b286ab899c008c2cb05e8be99814807e7fbbd33f0c0c960470826e5ac82/typer-0.23.1-py3-none-any.whl", hash = "sha256:3291ad0d3c701cbf522012faccfbb29352ff16ad262db2139e6b01f15781f14e", size = 56813, upload-time = "2026-02-13T10:04:32.008Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "types-python-dateutil"
version = "2.9.0.20260518"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8d/e8/c01bdf0d7c3659428c091fbd693177093639565bcbc86bc20098e6d37cc6/types_python_dateutil-2.9.0.20260518.tar.gz", hash = "sha256:51f02dc03b61c7f6a07df45797d4dfe8a1aa47f0b7db9ad89f6fd3a1a70e1b51", size = 17082, upload-time = "2026-05-18T06:05:24.508Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/8d/e8/c01bdf0d7c3659428c091fbd693177093639565bcbc86bc20098e6d37cc6/types_python_dateutil-2.9.0.20260518.tar.gz", hash = "sha256:51f02dc03b61c7f6a07df45797d4dfe8a1aa47f0b7db9ad89f6fd3a1a70e1b51", size = 17082, upload-time = "2026-05-18T06:05:24.508Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/36/22/169273273ca34e9ab0ae2f387ba72ed7e09faaaf834da01d6b89c2bea71a/types_python_dateutil-2.9.0.20260518-py3-none-any.whl", hash = "sha256:d6a9c5bd0de61460c8fdef8ab2b400f956a1a1075cce08d4e2b4434e478c50b8", size = 18431, upload-time = "2026-05-18T06:05:23.641Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/36/22/169273273ca34e9ab0ae2f387ba72ed7e09faaaf834da01d6b89c2bea71a/types_python_dateutil-2.9.0.20260518-py3-none-any.whl", hash = "sha256:d6a9c5bd0de61460c8fdef8ab2b400f956a1a1075cce08d4e2b4434e478c50b8", size = 18431, upload-time = "2026-05-18T06:05:23.641Z" },
]
[[package]]
name = "types-pyyaml"
version = "6.0.12.20260518"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b8/83/4a1afc3fbfcf5b8d46fc390cd95ed6b0dc9010a265f4e9f46314efffa37a/types_pyyaml-6.0.12.20260518.tar.gz", hash = "sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466", size = 17850, upload-time = "2026-05-18T06:01:58.675Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b8/83/4a1afc3fbfcf5b8d46fc390cd95ed6b0dc9010a265f4e9f46314efffa37a/types_pyyaml-6.0.12.20260518.tar.gz", hash = "sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466", size = 17850, upload-time = "2026-05-18T06:01:58.675Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/06/a2/c01db32be2ae7d6a1689972f3c492b149ee4e164b12fdfd9f64b50888215/types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd", size = 20312, upload-time = "2026-05-18T06:01:57.368Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/06/a2/c01db32be2ae7d6a1689972f3c492b149ee4e164b12fdfd9f64b50888215/types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd", size = 20312, upload-time = "2026-05-18T06:01:57.368Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "tzdata"
version = "2026.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" },
]
[[package]]
name = "uri-template"
version = "1.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
]
[[package]]
name = "urllib3"
version = "2.7.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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]]
name = "uvicorn"
version = "0.49.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "click" },
{ name = "h11" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" },
]
[[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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.5.1"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "distlib" },
{ name = "filelock" },
@@ -4825,180 +5152,180 @@ dependencies = [
{ name = "python-discovery" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" },
]
[[package]]
name = "watchdog"
version = "6.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
- { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
- { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
- { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
- { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
- { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
- { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
- { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
- { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
- { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
- { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
- { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
- { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
- { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
- { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
- { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
- { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
- { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
- { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
- { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
- { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
- { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
- { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
- { 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" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
+wheels = [
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/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" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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.8.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" },
]
[[package]]
name = "webcolors"
version = "25.10.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" },
]
[[package]]
name = "werkzeug"
version = "3.1.8"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "markupsafe" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" },
]
[[package]]
name = "wheel"
version = "0.47.0"
-source = { registry = "https://pypi.org/simple" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
dependencies = [
{ name = "packaging" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" },
]
[[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" },
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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 = "zipp"
version = "4.1.0"
-source = { registry = "https://pypi.org/simple" }
-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" }
+source = { registry = "https://pkgs.safetycli.com/repository/fgo-inc/pypi/simple/" }
+sdist = { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" },
+ { url = "https://pkgs.safetycli.com/package/fgo-inc/pypi/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" },
]