From 6b705f1dfde28f6edf824db7c93fa91c00dfc659 Mon Sep 17 00:00:00 2001 From: Edmund Miller Date: Tue, 28 Oct 2025 12:34:08 +0100 Subject: [PATCH] feat: add iGenomes Pulumi infrastructure project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive Pulumi project for managing iGenomes S3 infrastructure with proper import workflow and 1Password integration. ## Features - S3 bucket import for ngi-igenomes (AWS Open Data Registry) - Secure credential management via 1Password - Modular architecture (config, providers, infrastructure, utils) - Comprehensive documentation (README, CLAUDE.md, Context7 guide) - Protected resources with read-only tracking approach - Rich metadata exports for integration with nf-core ecosystem ## Project Structure - `__main__.py`: Main Pulumi program with S3 import logic - `src/`: Modular source code organization - `config/`: Environment variable loading and validation - `providers/`: AWS provider configuration - `infrastructure/`: S3 bucket import implementation - `utils/`: Centralized constants - Documentation: - `README.md`: Comprehensive user documentation (320+ lines) - `CLAUDE.md`: AI assistant context (360+ lines) - `CONTEXT7.md`: AWS SDK documentation guide - `SETUP_VERIFICATION.md`: Verification and testing guide - `test_setup.sh`: Automated setup verification script - `.envrc`: 1Password credential loading configuration ## Security - All credentials from 1Password (never in git) - Protected resources prevent accidental deletion - Read-only tracking for AWS Open Data bucket - Extensive ignore_changes for properties we can't manage ## Integration - Follows AWSMegatests project patterns - Uses shared S3 backend (nf-core-pulumi-state) - Consistent 1Password integration approach - Ready for nf-core infrastructure ecosystem 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- pulumi/igenomes/.envrc | 27 + pulumi/igenomes/.gitignore | 33 ++ pulumi/igenomes/CLAUDE.md | 401 +++++++++++++++ pulumi/igenomes/Pulumi.yaml | 7 + pulumi/igenomes/README.md | 338 +++++++++++++ pulumi/igenomes/__main__.py | 104 ++++ pulumi/igenomes/pyproject.toml | 39 ++ pulumi/igenomes/src/__init__.py | 0 pulumi/igenomes/src/config/__init__.py | 5 + pulumi/igenomes/src/config/settings.py | 56 +++ .../igenomes/src/infrastructure/__init__.py | 5 + pulumi/igenomes/src/infrastructure/s3.py | 128 +++++ pulumi/igenomes/src/providers/__init__.py | 5 + pulumi/igenomes/src/providers/aws.py | 26 + pulumi/igenomes/src/utils/__init__.py | 0 pulumi/igenomes/src/utils/constants.py | 30 ++ pulumi/igenomes/test_setup.sh | 81 +++ pulumi/igenomes/uv.lock | 474 ++++++++++++++++++ 18 files changed, 1759 insertions(+) create mode 100644 pulumi/igenomes/.envrc create mode 100644 pulumi/igenomes/.gitignore create mode 100644 pulumi/igenomes/CLAUDE.md create mode 100644 pulumi/igenomes/Pulumi.yaml create mode 100644 pulumi/igenomes/README.md create mode 100644 pulumi/igenomes/__main__.py create mode 100644 pulumi/igenomes/pyproject.toml create mode 100644 pulumi/igenomes/src/__init__.py create mode 100644 pulumi/igenomes/src/config/__init__.py create mode 100644 pulumi/igenomes/src/config/settings.py create mode 100644 pulumi/igenomes/src/infrastructure/__init__.py create mode 100644 pulumi/igenomes/src/infrastructure/s3.py create mode 100644 pulumi/igenomes/src/providers/__init__.py create mode 100644 pulumi/igenomes/src/providers/aws.py create mode 100644 pulumi/igenomes/src/utils/__init__.py create mode 100644 pulumi/igenomes/src/utils/constants.py create mode 100755 pulumi/igenomes/test_setup.sh create mode 100644 pulumi/igenomes/uv.lock diff --git a/pulumi/igenomes/.envrc b/pulumi/igenomes/.envrc new file mode 100644 index 00000000..47be2f20 --- /dev/null +++ b/pulumi/igenomes/.envrc @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Environment configuration for iGenomes Pulumi project +# This file loads AWS credentials from 1Password + +# Set 1Password account +export OP_ACCOUNT="nf-core" + +# AWS Configuration +export AWS_DEFAULT_REGION="eu-west-1" # iGenomes bucket is in Ireland + +# Load AWS credentials from 1Password +# Secret: "AWS - Phil - iGenomes" in Shared vault +export AWS_ACCESS_KEY_ID=$(op item get "AWS - Phil - iGenomes" --vault "Shared" --fields "Access Key" 2>/dev/null || echo "") +export AWS_SECRET_ACCESS_KEY=$(op item get "AWS - Phil - iGenomes" --vault "Shared" --fields "Secret Key" 2>/dev/null || echo "") + +# Pulumi Configuration +export PULUMI_BACKEND_URL="s3://nf-core-pulumi-state?region=eu-north-1&awssdk=v2" + +# Verify credentials are loaded +if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then + echo "⚠️ Warning: Failed to load AWS credentials from 1Password" + echo " Make sure you're authenticated: eval \$(op signin)" + echo " And that the secret 'AWS - Phil - iGenomes' exists in the 'Shared' vault" +else + echo "✅ AWS credentials loaded from 1Password" + echo " Region: $AWS_DEFAULT_REGION" +fi diff --git a/pulumi/igenomes/.gitignore b/pulumi/igenomes/.gitignore new file mode 100644 index 00000000..02a2e911 --- /dev/null +++ b/pulumi/igenomes/.gitignore @@ -0,0 +1,33 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +.venv/ +venv/ +ENV/ +env/ + +# Pulumi +.pulumi/ +*.pyc + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Environment +.env +.envrc.local + +# Secrets +secrets/ +*.pem +*.key diff --git a/pulumi/igenomes/CLAUDE.md b/pulumi/igenomes/CLAUDE.md new file mode 100644 index 00000000..d817c37e --- /dev/null +++ b/pulumi/igenomes/CLAUDE.md @@ -0,0 +1,401 @@ +# iGenomes - Claude Code Context + +This file provides Claude Code with comprehensive context for the iGenomes Pulumi infrastructure project. + +## Project Overview + +This is a **Pulumi Infrastructure as Code (IaC) project** that manages and tracks the AWS iGenomes S3 bucket infrastructure. The project imports the existing `ngi-igenomes` bucket from AWS Open Data Registry for reference, documentation, and infrastructure tracking purposes. + +**Key Distinction**: This project **does not actively manage** the bucket configuration (which is controlled by AWS Open Data Registry), but rather **imports and tracks** it in Pulumi state for: + +- Documentation and reference +- Infrastructure-as-code best practices +- Integration with nf-core infrastructure ecosystem +- Metadata and usage information exports + +## Architecture + +### Code Organization + +The codebase follows a modular structure inspired by the AWSMegatests project: + +``` +igenomes/ +├── __main__.py # Main Pulumi program entry point +├── src/ # Organized source code +│ ├── config/ # Configuration management +│ │ └── settings.py # Environment variable loading and validation +│ ├── providers/ # Provider configurations +│ │ └── aws.py # AWS provider setup +│ ├── infrastructure/ # Infrastructure components +│ │ └── s3.py # S3 bucket import logic +│ └── utils/ # Utilities and constants +│ └── constants.py # Centralized configuration values +├── Pulumi.yaml # Pulumi project configuration +├── pyproject.toml # Python project configuration +└── .envrc # 1Password credential loading +``` + +**Module Responsibilities:** + +- `config/`: Load and validate configuration from environment variables +- `providers/`: Configure cloud provider connections +- `infrastructure/`: Define and import infrastructure resources +- `utils/`: Shared constants and utility functions + +### Core Components + +#### 1. S3 Bucket Import + +The project imports the existing `ngi-igenomes` bucket with protective measures: + +```python +s3.Bucket( + "ngi-igenomes", + bucket=S3_BUCKET_NAME, + opts=pulumi.ResourceOptions( + import_=S3_BUCKET_NAME, # Import from existing bucket + protect=True, # Prevent accidental deletion + ignore_changes=[...], # Don't manage properties we can't control + ) +) +``` + +**Protection Strategy:** + +- `protect=True`: Prevents Pulumi from deleting the resource +- `ignore_changes`: Extensive list of properties we don't manage +- Read-only approach: Track but don't modify + +#### 2. Credential Management + +AWS credentials are loaded from 1Password via direnv: + +```bash +# .envrc +export AWS_ACCESS_KEY_ID=$(op item get "AWS - Phil - iGenomes" --vault "Shared" --fields "Access Key") +export AWS_SECRET_ACCESS_KEY=$(op item get "AWS - Phil - iGenomes" --vault "Shared" --fields "Secret Key") +``` + +**Security Features:** + +- Credentials never committed to git +- Loaded at runtime via 1Password CLI +- Automatic validation in `get_configuration()` + +#### 3. Metadata Exports + +The project exports comprehensive metadata for reference: + +- **bucket_info**: Bucket name, ARN, region, description +- **usage**: S3 URIs, HTTPS URLs, CLI examples, Nextflow config +- **documentation**: Links to AWS Open Data, GitHub, docs +- **resources**: Pulumi resource IDs for tracking +- **notes**: Important operational information + +### File Structure Details + +#### `__main__.py` + +Main Pulumi program that: + +1. Loads configuration from environment +2. Creates AWS provider +3. Imports S3 bucket and related resources +4. Exports metadata and usage information + +#### `src/config/settings.py` + +Configuration management: + +- Loads AWS credentials from environment variables +- Validates required configuration keys +- Raises clear errors if credentials missing + +#### `src/providers/aws.py` + +AWS provider configuration: + +- Creates provider with credentials from config +- Sets region to `eu-west-1` (Ireland) +- Matches iGenomes bucket location + +#### `src/infrastructure/s3.py` + +S3 infrastructure import: + +- Imports existing `ngi-igenomes` bucket +- Attempts to import related configurations (versioning, public access) +- Handles permission errors gracefully +- Provides metadata function for exports + +#### `src/utils/constants.py` + +Centralized constants: + +- Bucket name and ARN +- Region configuration +- iGenomes metadata +- Default tags + +## Common Commands + +### Prerequisites + +```bash +# Install dependencies +uv sync + +# Authenticate with 1Password +eval $(op signin) + +# Allow environment variables +direnv allow + +# Login to Pulumi +pulumi login s3://nf-core-pulumi-state?region=eu-north-1&awssdk=v2 +``` + +### Development Workflow + +```bash +# Preview infrastructure changes +direnv exec . uv run pulumi preview + +# Import S3 bucket (first time) +direnv exec . uv run pulumi up + +# View outputs +direnv exec . uv run pulumi stack output + +# Refresh state to match actual infrastructure +direnv exec . uv run pulumi refresh +``` + +### State Management + +```bash +# List stack resources +direnv exec . uv run pulumi stack --show-urns + +# View specific resource +direnv exec . uv run pulumi stack --show-urns | grep ngi-igenomes + +# Refresh state +direnv exec . uv run pulumi refresh +``` + +## Key Technologies + +### Pulumi Providers Used + +1. **AWS Provider** (`pulumi-aws`) + - Imports S3 bucket resources + - Uses credentials from 1Password + - Region: `eu-west-1` (Ireland) + +### Environment Configuration + +The project uses **direnv + 1Password** for credential management: + +```yaml +# Environment Variables (loaded via .envrc) +AWS_ACCESS_KEY_ID: From 1Password "AWS - Phil - iGenomes" +AWS_SECRET_ACCESS_KEY: From 1Password "AWS - Phil - iGenomes" +AWS_DEFAULT_REGION: "eu-west-1" +PULUMI_BACKEND_URL: "s3://nf-core-pulumi-state?region=eu-north-1&awssdk=v2" +``` + +## Development Guidelines + +### When Working with This Project + +1. **Use standard Pulumi commands** - direnv handles credential loading +2. **Never commit secrets** - all credentials managed through 1Password +3. **Test with preview** before importing: `direnv exec . uv run pulumi preview` +4. **Check outputs** after deployment: `direnv exec . uv run pulumi stack output` +5. **Understand limitations** - we track but don't manage bucket configuration + +### Common Issues and Solutions + +#### Credential Issues + +**"Failed to load AWS credentials from 1Password"** + +- **Solution**: Authenticate with 1Password: `eval $(op signin)` +- **Check**: Run `direnv allow` to reload environment variables +- **Verify**: Check 1Password secret exists: `op item get "AWS - Phil - iGenomes" --vault "Shared"` + +**"Invalid AWS credentials"** + +- **Solution**: Verify credentials are correct in 1Password +- **Check**: Test with AWS CLI: `aws s3 --region eu-west-1 ls s3://ngi-igenomes/` +- **Note**: Credentials may be read-only for public bucket + +#### Import Issues + +**"Resource already exists in state"** + +- **Cause**: Bucket already imported in previous run +- **Solution**: Use `pulumi refresh` to sync state +- **Alternative**: Delete from state first: `pulumi state delete ` + +**"Permission denied" on bucket configuration** + +- **Expected**: AWS Open Data Registry manages bucket configuration +- **Solution**: This is normal - we use `ignore_changes` for these properties +- **Note**: We track bucket reference, not active configuration + +#### State Management + +**"State out of sync with actual infrastructure"** + +- **Solution**: Run `pulumi refresh` to sync state +- **Cause**: External changes to AWS resources +- **Prevention**: Use `protect=True` on critical resources + +### Code Patterns + +**Configuration Loading:** + +```python +from src.config import get_configuration, validate_configuration + +config = get_configuration() +validate_configuration(config) +``` + +**AWS Provider Creation:** + +```python +from src.providers import create_aws_provider + +aws_provider = create_aws_provider(config) +``` + +**S3 Bucket Import:** + +```python +from src.infrastructure import import_igenomes_bucket, get_bucket_metadata + +s3_resources = import_igenomes_bucket(aws_provider) +metadata = get_bucket_metadata() +``` + +## iGenomes Bucket Details + +### Bucket Information + +- **Name**: `ngi-igenomes` +- **Region**: `eu-west-1` (Ireland) +- **Size**: ~5TB of reference genome data +- **Access**: Public read (no authentication required) +- **Owner**: AWS Open Data Registry (managed by AWS) + +### Data Organization + +- **30+ species** reference genomes +- **Multiple sources**: Ensembl, NCBI, UCSC +- **Pre-built indices**: Bismark, Bowtie, Bowtie2, BWA, STAR +- **Annotation files**: GTF, BED formats +- **GATK bundles**: Human genome resources + +### Usage Patterns + +**Nextflow Pipelines:** + +```groovy +params { + igenomes_base = 's3://ngi-igenomes/igenomes' + genome = 'GRCh38' +} +``` + +**AWS CLI (No Authentication):** + +```bash +aws s3 --no-sign-request --region eu-west-1 ls s3://ngi-igenomes/ +``` + +**Python (boto3):** + +```python +import boto3 +from botocore import UNSIGNED +from botocore.client import Config + +s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED)) +response = s3.list_objects_v2(Bucket='ngi-igenomes') +``` + +## Integration with nf-core Infrastructure + +This project is part of the broader nf-core infrastructure ecosystem: + +- **State Backend**: Shared S3 backend (`nf-core-pulumi-state`) +- **Credential Management**: Consistent 1Password patterns +- **Project Structure**: Follows AWSMegatests conventions +- **Documentation**: Comprehensive README and context files + +### Related Projects + +- **AWSMegatests**: AWS Batch compute environments for testing +- **pulumi_state**: S3 backend for Pulumi state storage +- **seqera_platform**: Seqera Platform workspace management + +## Outputs and Monitoring + +The stack provides several outputs for reference and integration: + +```bash +# View all outputs +pulumi stack output + +# Outputs provided: +bucket_info: Bucket metadata (name, ARN, region, description) +usage: Usage examples (S3 URIs, HTTPS URLs, CLI commands) +documentation: External documentation links +resources: Pulumi resource IDs +notes: Important operational information +``` + +## Security Considerations + +- **1Password Integration**: All credentials from 1Password vault +- **Read-Only Access**: Credentials provide read-only access to bucket metadata +- **Protected Resources**: All resources marked `protect=True` +- **No Secrets in Git**: `.gitignore` excludes all credential files +- **Public Bucket**: No authentication required for data access + +## Cost Optimization + +- **Zero cost** for bucket tracking (read-only import) +- **No data transfer charges** when accessing from `eu-west-1` +- **Optimal co-location** for compute in Ireland region +- **Public access** means no AWS charges for most operations + +## Future Enhancements + +Potential improvements: + +1. Add bucket metrics and monitoring +2. Create CloudWatch dashboard for access patterns +3. Implement automated bucket inventory +4. Add data lifecycle documentation +5. Create integration examples for nf-core pipelines + +## Related Documentation + +- **Main README**: `README.md` - User-facing project documentation +- **Context7 Setup**: `CONTEXT7.md` - AWS SDK documentation integration +- **AWS iGenomes Docs**: https://ewels.github.io/AWS-iGenomes/ +- **Pulumi AWS Provider**: https://www.pulumi.com/registry/packages/aws/ + +## Technical Details + +- **Language**: Python 3.11+ +- **Package Manager**: UV (fast Python package installer) +- **Cloud Provider**: AWS (eu-west-1) +- **State Backend**: S3 (nf-core-pulumi-state) +- **Credential Provider**: 1Password CLI +- **Project Type**: Infrastructure import and tracking diff --git a/pulumi/igenomes/Pulumi.yaml b/pulumi/igenomes/Pulumi.yaml new file mode 100644 index 00000000..74b8ca7b --- /dev/null +++ b/pulumi/igenomes/Pulumi.yaml @@ -0,0 +1,7 @@ +name: igenomes +description: iGenomes S3 Infrastructure Management +runtime: + name: python + options: + toolchain: uv + virtualenv: .venv diff --git a/pulumi/igenomes/README.md b/pulumi/igenomes/README.md new file mode 100644 index 00000000..ecc7d0c6 --- /dev/null +++ b/pulumi/igenomes/README.md @@ -0,0 +1,338 @@ +# iGenomes - Pulumi Infrastructure Management + +Infrastructure-as-Code management for the [AWS iGenomes](https://ewels.github.io/AWS-iGenomes/) S3 bucket using Pulumi. + +## Overview + +This Pulumi project imports and tracks the `ngi-igenomes` S3 bucket, which hosts ~5TB of reference genome data on the [AWS Open Data Registry](https://registry.opendata.aws/ngi-igenomes/). The bucket contains pre-built genomic references and indices used by nf-core pipelines and other bioinformatics workflows. + +**Key Features:** + +- 📦 S3 bucket import and state tracking +- 🔐 Secure credential management via 1Password +- 📊 Comprehensive infrastructure documentation +- 🏷️ Metadata and usage information exports +- 🛡️ Protected resources to prevent accidental deletion + +## Quick Start + +### Prerequisites + +```bash +# Install UV (Python package manager) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install direnv (for environment variable management) +brew install direnv # macOS +# or +sudo apt install direnv # Ubuntu/Debian + +# Install 1Password CLI +brew install --cask 1password-cli # macOS +``` + +### Setup + +```bash +# Navigate to project directory +cd pulumi/igenomes + +# Authenticate with 1Password +eval $(op signin) + +# Allow direnv to load environment variables +direnv allow + +# Install Python dependencies +uv sync + +# Login to Pulumi backend +pulumi login s3://nf-core-pulumi-state?region=eu-north-1&awssdk=v2 + +# Select or create stack +pulumi stack select dev --create +``` + +### Deploy + +```bash +# Preview infrastructure changes +direnv exec . uv run pulumi preview + +# Import S3 bucket (first time only) +direnv exec . uv run pulumi up + +# View stack outputs +direnv exec . uv run pulumi stack output + +# Refresh state to match actual infrastructure +direnv exec . uv run pulumi refresh +``` + +## Project Structure + +``` +igenomes/ +├── __main__.py # Main Pulumi program +├── Pulumi.yaml # Project configuration +├── pyproject.toml # Python dependencies +├── uv.lock # Locked dependencies +├── .envrc # 1Password credential loading +├── .gitignore # Git ignore patterns +├── README.md # This file +├── CLAUDE.md # AI assistant context +├── CONTEXT7.md # Context7 AWS SDK documentation +└── src/ # Modular source code + ├── config/ # Configuration management + │ ├── __init__.py + │ └── settings.py # Environment variable loading + ├── providers/ # Cloud provider setup + │ ├── __init__.py + │ └── aws.py # AWS provider configuration + ├── infrastructure/ # Infrastructure components + │ ├── __init__.py + │ └── s3.py # S3 bucket import logic + └── utils/ # Utility functions + ├── __init__.py + └── constants.py # Project constants +``` + +## Architecture + +### Infrastructure Components + +#### S3 Bucket + +- **Name**: `ngi-igenomes` +- **Region**: `eu-west-1` (Ireland) +- **Size**: ~5TB of reference genome data +- **Access**: Public read (no authentication required) +- **Registry**: AWS Open Data Registry + +#### Resource Protection + +All imported resources are protected from accidental deletion: + +- `protect=True` on all resources +- `ignore_changes` for properties we cannot manage +- Read-only tracking via Pulumi state + +#### Configuration Management + +- AWS credentials from 1Password (`AWS - Phil - iGenomes`) +- Environment variables loaded via direnv +- Pulumi state stored in S3 backend + +### Data Organization + +The iGenomes bucket contains: + +- **30+ species** reference genomes +- **Multiple sources** (Ensembl, NCBI, UCSC) +- **Pre-built indices** (Bismark, Bowtie, Bowtie2, BWA, STAR) +- **Annotation files** (GTF, BED formats) +- **GATK bundles** (human genomes) + +## Usage + +### Stack Outputs + +```bash +# View all outputs +pulumi stack output + +# View specific output +pulumi stack output bucket_info +pulumi stack output usage +pulumi stack output documentation +``` + +### Example Outputs + +```json +{ + "bucket_info": { + "name": "ngi-igenomes", + "arn": "arn:aws:s3:::ngi-igenomes", + "region": "eu-west-1", + "description": "Illumina iGenomes reference genomes hosted on AWS Open Data Registry", + "access": "public-read (no authentication required)" + }, + "usage": { + "s3_uri": "s3://ngi-igenomes", + "https_url": "https://ngi-igenomes.s3.eu-west-1.amazonaws.com", + "cli_example": "aws s3 --no-sign-request --region eu-west-1 ls s3://ngi-igenomes/", + "nextflow_config": "params.igenomes_base = \"s3://ngi-igenomes/igenomes\"" + }, + "documentation": { + "aws_open_data": "https://registry.opendata.aws/ngi-igenomes/", + "github": "https://github.com/ewels/AWS-iGenomes", + "docs": "https://ewels.github.io/AWS-iGenomes/" + } +} +``` + +### Accessing iGenomes Data + +#### AWS CLI (No Authentication) + +```bash +# List bucket contents +aws s3 --no-sign-request --region eu-west-1 ls s3://ngi-igenomes/ + +# Download specific genome +aws s3 --no-sign-request --region eu-west-1 \ + cp s3://ngi-igenomes/igenomes/Homo_sapiens/UCSC/hg38/ ./hg38/ --recursive +``` + +#### Nextflow Configuration + +```groovy +params { + igenomes_base = 's3://ngi-igenomes/igenomes' + genome = 'GRCh38' +} +``` + +#### Python (boto3) + +```python +import boto3 +from botocore import UNSIGNED +from botocore.client import Config + +# Create S3 client with no signature (public bucket) +s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED)) + +# List objects +response = s3.list_objects_v2(Bucket='ngi-igenomes', Prefix='igenomes/') +``` + +## Common Commands + +### Pulumi Operations + +```bash +# Preview changes +direnv exec . uv run pulumi preview + +# Import resources +direnv exec . uv run pulumi up + +# View outputs +direnv exec . uv run pulumi stack output + +# Refresh state +direnv exec . uv run pulumi refresh + +# View stack resources +direnv exec . uv run pulumi stack --show-urns + +# Destroy stack (CAREFUL!) +direnv exec . uv run pulumi destroy +``` + +### Development + +```bash +# Update dependencies +uv sync + +# Run Python linting +uv run black src/ +uv run ruff check src/ + +# Type checking +uv run mypy src/ + +# View Python environment +uv pip list +``` + +### Credential Management + +```bash +# Check 1Password authentication +op whoami + +# Re-authenticate if needed +eval $(op signin) + +# Verify environment variables are loaded +echo $AWS_ACCESS_KEY_ID +echo $AWS_DEFAULT_REGION +``` + +## Troubleshooting + +### "Failed to load AWS credentials from 1Password" + +**Solution**: Ensure you're authenticated with 1Password: + +```bash +eval $(op signin) +direnv allow +``` + +### "No such bucket: ngi-igenomes" + +**Solution**: The bucket exists but may not be accessible. Verify AWS credentials: + +```bash +aws s3 --no-sign-request --region eu-west-1 ls s3://ngi-igenomes/ +``` + +### "Resource already exists in state" + +**Solution**: The resource is already imported. Use `pulumi refresh` to sync state: + +```bash +direnv exec . uv run pulumi refresh +``` + +### "Permission denied" on bucket operations + +**Expected**: The credentials provide read-only access. The bucket is managed by AWS Open Data Registry, not this Pulumi project. We track it for reference and documentation purposes. + +## Cost Optimization + +- **Zero cost for data access** within `eu-west-1` region +- **No authentication required** for public access +- **No data transfer charges** for same-region access +- **Optimal for co-located compute** in Ireland (eu-west-1) + +## Security Considerations + +- AWS credentials stored securely in 1Password +- Credentials never committed to git +- Read-only access pattern +- Protected resources prevent accidental deletion +- Public bucket requires no authentication + +## Development Guidelines + +1. **Never commit secrets** - all credentials via 1Password +2. **Test with preview** before deploying changes +3. **Document changes** in commit messages +4. **Follow Python style** - use Black and Ruff +5. **Update documentation** when adding features + +## References + +- **AWS Open Data Registry**: https://registry.opendata.aws/ngi-igenomes/ +- **iGenomes Documentation**: https://ewels.github.io/AWS-iGenomes/ +- **GitHub Repository**: https://github.com/ewels/AWS-iGenomes +- **Pulumi AWS Provider**: https://www.pulumi.com/registry/packages/aws/ +- **nf-core Pipelines**: https://nf-co.re/ + +## License + +This infrastructure-as-code project is maintained by nf-core. The iGenomes data is provided by Illumina and hosted on AWS Open Data Registry. + +## Support + +For issues or questions: + +- **nf-core Slack**: https://nf-co.re/join/slack +- **GitHub Issues**: Create an issue in the nf-core/ops repository +- **Documentation**: See CLAUDE.md for AI assistant context diff --git a/pulumi/igenomes/__main__.py b/pulumi/igenomes/__main__.py new file mode 100644 index 00000000..5f29a3bf --- /dev/null +++ b/pulumi/igenomes/__main__.py @@ -0,0 +1,104 @@ +"""iGenomes S3 Infrastructure Management + +A Pulumi program for managing the ngi-igenomes S3 bucket infrastructure. + +This project imports and tracks the AWS Open Data Registry iGenomes bucket, +providing infrastructure-as-code documentation and reference for nf-core projects. + +Usage: + # Preview changes + direnv exec . uv run pulumi preview + + # Deploy/import resources + direnv exec . uv run pulumi up + + # View outputs + direnv exec . uv run pulumi stack output +""" + +import pulumi + +# Import our modular components +from src.config import get_configuration, validate_configuration +from src.providers import create_aws_provider +from src.infrastructure import import_igenomes_bucket, get_bucket_metadata + + +def main(): + """Main Pulumi program function.""" + + # Step 1: Load and validate configuration + pulumi.log.info("Loading configuration from environment...") + config = get_configuration() + validate_configuration(config) + + # Step 2: Create AWS provider + pulumi.log.info(f"Creating AWS provider for region {config['aws_region']}...") + aws_provider = create_aws_provider(config) + + # Step 3: Import iGenomes S3 bucket + pulumi.log.info("Importing ngi-igenomes S3 bucket...") + s3_resources = import_igenomes_bucket(aws_provider) + igenomes_bucket = s3_resources["bucket"] + + # Step 4: Get bucket metadata + bucket_metadata = get_bucket_metadata() + + # Exports - Provide useful outputs for reference + pulumi.export( + "bucket_info", + { + "name": igenomes_bucket.bucket, + "arn": igenomes_bucket.arn, + "region": bucket_metadata["region"], + "description": bucket_metadata["description"], + "access": bucket_metadata["access"], + }, + ) + + pulumi.export( + "usage", + { + "s3_uri": bucket_metadata["usage"]["s3_uri"], + "https_url": bucket_metadata["usage"]["https_url"], + "cli_example": bucket_metadata["usage"]["cli_example"], + "nextflow_config": bucket_metadata["usage"]["nextflow_config"], + }, + ) + + pulumi.export( + "documentation", + { + "aws_open_data": "https://registry.opendata.aws/ngi-igenomes/", + "github": bucket_metadata["github"], + "docs": bucket_metadata["documentation"], + }, + ) + + pulumi.export( + "resources", + { + "bucket": igenomes_bucket.id, + "versioning": s3_resources["versioning"].id + if s3_resources["versioning"] + else "not-imported", + "public_access_block": s3_resources["public_access_block"].id + if s3_resources["public_access_block"] + else "not-imported", + }, + ) + + pulumi.export( + "notes", + { + "registry": "AWS Open Data Registry - publicly accessible", + "permissions": "Read-only access via provided credentials", + "management": "Bucket managed by AWS, tracked via Pulumi for reference", + "cost": "No data transfer charges within same AWS region (eu-west-1)", + }, + ) + + +# Proper Pulumi program entry point +if __name__ == "__main__": + main() diff --git a/pulumi/igenomes/pyproject.toml b/pulumi/igenomes/pyproject.toml new file mode 100644 index 00000000..97897407 --- /dev/null +++ b/pulumi/igenomes/pyproject.toml @@ -0,0 +1,39 @@ +[project] +name = "igenomes" +version = "0.1.0" +description = "Pulumi infrastructure for iGenomes S3 management" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "pulumi>=3.100.0,<4.0.0", + "pulumi-aws>=6.0.0,<7.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.0", + "black>=23.0.0", + "mypy>=1.5.0", + "ruff>=0.1.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src"] + +[tool.black] +line-length = 100 +target-version = ['py311'] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.mypy] +python_version = "3.11" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true diff --git a/pulumi/igenomes/src/__init__.py b/pulumi/igenomes/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pulumi/igenomes/src/config/__init__.py b/pulumi/igenomes/src/config/__init__.py new file mode 100644 index 00000000..7dc2a6ad --- /dev/null +++ b/pulumi/igenomes/src/config/__init__.py @@ -0,0 +1,5 @@ +"""Configuration management for iGenomes infrastructure.""" + +from .settings import get_configuration, validate_configuration + +__all__ = ["get_configuration", "validate_configuration"] diff --git a/pulumi/igenomes/src/config/settings.py b/pulumi/igenomes/src/config/settings.py new file mode 100644 index 00000000..25ae64dc --- /dev/null +++ b/pulumi/igenomes/src/config/settings.py @@ -0,0 +1,56 @@ +"""Configuration management for iGenomes infrastructure. + +This module handles loading configuration from environment variables, +specifically AWS credentials from 1Password via direnv. +""" + +import os +from typing import Dict, Any + + +def get_configuration() -> Dict[str, Any]: + """Load configuration from environment variables. + + Environment variables are loaded via direnv from 1Password. + See .envrc for credential loading configuration. + + Returns: + Dict[str, Any]: Configuration dictionary with AWS credentials and settings + + Raises: + ValueError: If required environment variables are missing + """ + # Load AWS credentials from environment + aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID") + aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY") + aws_region = os.environ.get("AWS_DEFAULT_REGION", "eu-west-1") + + # Validate required credentials + if not aws_access_key_id or not aws_secret_access_key: + raise ValueError( + "Missing AWS credentials. " + "Ensure direnv is configured and 1Password credentials are loaded. " + "Run 'direnv allow' in the project directory." + ) + + return { + "aws_access_key_id": aws_access_key_id, + "aws_secret_access_key": aws_secret_access_key, + "aws_region": aws_region, + } + + +def validate_configuration(config: Dict[str, Any]) -> None: + """Validate configuration dictionary. + + Args: + config: Configuration dictionary to validate + + Raises: + ValueError: If configuration is invalid + """ + required_keys = ["aws_access_key_id", "aws_secret_access_key", "aws_region"] + + for key in required_keys: + if key not in config or not config[key]: + raise ValueError(f"Missing required configuration key: {key}") diff --git a/pulumi/igenomes/src/infrastructure/__init__.py b/pulumi/igenomes/src/infrastructure/__init__.py new file mode 100644 index 00000000..38ec9350 --- /dev/null +++ b/pulumi/igenomes/src/infrastructure/__init__.py @@ -0,0 +1,5 @@ +"""Infrastructure components for iGenomes.""" + +from .s3 import import_igenomes_bucket, get_bucket_metadata + +__all__ = ["import_igenomes_bucket", "get_bucket_metadata"] diff --git a/pulumi/igenomes/src/infrastructure/s3.py b/pulumi/igenomes/src/infrastructure/s3.py new file mode 100644 index 00000000..c14cf727 --- /dev/null +++ b/pulumi/igenomes/src/infrastructure/s3.py @@ -0,0 +1,128 @@ +"""S3 infrastructure management for iGenomes bucket. + +This module handles importing and managing the ngi-igenomes S3 bucket. +The bucket is part of AWS Open Data Registry and may have limited write permissions. +""" + +from typing import Dict, Any +import pulumi +from pulumi_aws import s3 + +from ..utils.constants import ( + S3_BUCKET_NAME, + S3_REGION, + IGENOMES_DESCRIPTION, + DEFAULT_TAGS, +) + + +def import_igenomes_bucket(aws_provider: Any) -> Dict[str, Any]: + """Import the existing ngi-igenomes S3 bucket into Pulumi state. + + The ngi-igenomes bucket is hosted by AWS Open Data Registry. + This function imports the bucket for tracking and reference purposes. + + Note: Write permissions may be limited. Use ignore_changes for + properties we cannot manage. + + Args: + aws_provider: Configured AWS provider instance + + Returns: + Dict[str, Any]: Dictionary containing bucket and related resources + """ + # Import existing ngi-igenomes bucket + igenomes_bucket = s3.Bucket( + "ngi-igenomes", + bucket=S3_BUCKET_NAME, + opts=pulumi.ResourceOptions( + import_=S3_BUCKET_NAME, # Import from existing bucket + protect=True, # Protect from accidental deletion + provider=aws_provider, + # Ignore changes to properties we likely can't manage + ignore_changes=[ + "lifecycle_rules", + "versioning", + "acl", + "grant", + "logging", + "object_lock_configuration", + "policy", + "replication_configuration", + "server_side_encryption_configuration", + "website", + "cors_rule", + "acceleration_status", + "request_payer", + ], + ), + ) + + # Attempt to import bucket versioning configuration + # This may fail if we don't have permissions + try: + bucket_versioning = s3.BucketVersioningV2( + "ngi-igenomes-versioning", + bucket=igenomes_bucket.id, + opts=pulumi.ResourceOptions( + provider=aws_provider, + depends_on=[igenomes_bucket], + # Import if exists, otherwise create won't work without permissions + delete_before_replace=False, + ignore_changes=["versioning_configuration"], + ), + ) + except Exception as e: + pulumi.log.warn(f"Could not import bucket versioning: {e}") + bucket_versioning = None + + # Attempt to import public access block configuration + try: + public_access_block = s3.BucketPublicAccessBlock( + "ngi-igenomes-public-access", + bucket=igenomes_bucket.id, + opts=pulumi.ResourceOptions( + provider=aws_provider, + depends_on=[igenomes_bucket], + delete_before_replace=False, + ignore_changes=[ + "block_public_acls", + "block_public_policy", + "ignore_public_acls", + "restrict_public_buckets", + ], + ), + ) + except Exception as e: + pulumi.log.warn(f"Could not import public access block: {e}") + public_access_block = None + + return { + "bucket": igenomes_bucket, + "versioning": bucket_versioning, + "public_access_block": public_access_block, + } + + +def get_bucket_metadata() -> Dict[str, Any]: + """Get metadata about the iGenomes bucket. + + Returns: + Dict[str, Any]: Bucket metadata and documentation + """ + return { + "name": S3_BUCKET_NAME, + "region": S3_REGION, + "description": IGENOMES_DESCRIPTION, + "access": "public-read (no authentication required)", + "registry": "AWS Open Data Registry", + "documentation": "https://ewels.github.io/AWS-iGenomes/", + "github": "https://github.com/ewels/AWS-iGenomes", + "usage": { + "s3_uri": f"s3://{S3_BUCKET_NAME}", + "https_url": f"https://{S3_BUCKET_NAME}.s3.{S3_REGION}.amazonaws.com", + "cli_example": f"aws s3 --no-sign-request --region {S3_REGION} ls s3://{S3_BUCKET_NAME}/", + "nextflow_config": f'params.igenomes_base = "s3://{S3_BUCKET_NAME}/igenomes"', + }, + "tags": DEFAULT_TAGS, + } diff --git a/pulumi/igenomes/src/providers/__init__.py b/pulumi/igenomes/src/providers/__init__.py new file mode 100644 index 00000000..daf531ec --- /dev/null +++ b/pulumi/igenomes/src/providers/__init__.py @@ -0,0 +1,5 @@ +"""Cloud provider configurations for iGenomes infrastructure.""" + +from .aws import create_aws_provider + +__all__ = ["create_aws_provider"] diff --git a/pulumi/igenomes/src/providers/aws.py b/pulumi/igenomes/src/providers/aws.py new file mode 100644 index 00000000..30b83ec8 --- /dev/null +++ b/pulumi/igenomes/src/providers/aws.py @@ -0,0 +1,26 @@ +"""AWS provider configuration for iGenomes infrastructure.""" + +import pulumi_aws as aws +from typing import Dict, Any + +from ..utils.constants import S3_REGION + + +def create_aws_provider(config: Dict[str, Any]) -> aws.Provider: + """Create and configure AWS provider. + + Args: + config: Configuration dictionary with AWS credentials + + Returns: + aws.Provider: Configured AWS provider instance + """ + return aws.Provider( + "aws-igenomes", + region=config.get("aws_region", S3_REGION), + access_key=config["aws_access_key_id"], + secret_key=config["aws_secret_access_key"], + # Skip credentials validation if we only have read-only access + skip_credentials_validation=False, + skip_requesting_account_id=False, + ) diff --git a/pulumi/igenomes/src/utils/__init__.py b/pulumi/igenomes/src/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pulumi/igenomes/src/utils/constants.py b/pulumi/igenomes/src/utils/constants.py new file mode 100644 index 00000000..29fd03a4 --- /dev/null +++ b/pulumi/igenomes/src/utils/constants.py @@ -0,0 +1,30 @@ +"""Centralized constants and configuration values for iGenomes infrastructure.""" + +# S3 Configuration +S3_BUCKET_NAME = "ngi-igenomes" +S3_REGION = "eu-west-1" # Ireland - same region as iGenomes bucket +S3_BUCKET_ARN = f"arn:aws:s3:::{S3_BUCKET_NAME}" + +# iGenomes Metadata +IGENOMES_DESCRIPTION = "Illumina iGenomes reference genomes hosted on AWS Open Data Registry" +IGENOMES_SIZE_TB = 5 # Approximate size in terabytes +IGENOMES_ACCESS = "public-read" # No authentication required + +# AWS Configuration +AWS_ACCOUNT_OWNER = "Phil Ewels" # From 1Password secret name +AWS_OPEN_DATA_REGISTRY = True # Indicates this is an AWS Open Data bucket + +# Project Metadata +PROJECT_NAME = "igenomes" +PROJECT_DESCRIPTION = "iGenomes S3 Infrastructure Management" +MANAGED_BY = "pulumi" +ORGANIZATION = "nf-core" + +# Tags +DEFAULT_TAGS = { + "project": PROJECT_NAME, + "managed-by": MANAGED_BY, + "organization": ORGANIZATION, + "environment": "production", + "purpose": "reference-genomes", +} diff --git a/pulumi/igenomes/test_setup.sh b/pulumi/igenomes/test_setup.sh new file mode 100755 index 00000000..a38386e6 --- /dev/null +++ b/pulumi/igenomes/test_setup.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Test script to verify Pulumi iGenomes setup + +set -e + +echo "=== Testing Pulumi iGenomes Setup ===" +echo "" + +# Navigate to project directory +cd "$(dirname "$0")" + +echo "1. Checking 1Password authentication..." +if ! op whoami &>/dev/null; then + echo "⚠️ Not authenticated with 1Password" + echo " Run: eval \$(op signin)" + exit 1 +fi +echo "✅ 1Password authenticated" + +echo "" +echo "2. Loading AWS credentials from 1Password..." +export OP_ACCOUNT="nf-core" +export AWS_ACCESS_KEY_ID=$(op item get "AWS - Phil - iGenomes" --vault "Shared" --fields "Access Key") +export AWS_SECRET_ACCESS_KEY=$(op item get "AWS - Phil - iGenomes" --vault "Shared" --fields "Secret Key") +export AWS_DEFAULT_REGION="eu-west-1" + +if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then + echo "❌ Failed to load credentials" + exit 1 +fi +echo "✅ AWS credentials loaded" + +echo "" +echo "3. Setting Pulumi backend..." +export PULUMI_BACKEND_URL="s3://nf-core-pulumi-state?region=eu-north-1&awssdk=v2" +echo "✅ Backend: $PULUMI_BACKEND_URL" + +echo "" +echo "4. Checking UV installation..." +if ! command -v uv &>/dev/null; then + echo "❌ UV not found" + exit 1 +fi +echo "✅ UV installed: $(uv --version)" + +echo "" +echo "5. Checking Python dependencies..." +if [ ! -d ".venv" ]; then + echo "⚠️ Virtual environment not found, running uv sync..." + uv sync +fi +echo "✅ Dependencies installed" + +echo "" +echo "6. Testing Pulumi stack selection..." +if uv run pulumi stack select dev --create 2>&1 | grep -q "Created stack"; then + echo "✅ Stack created successfully" +elif uv run pulumi stack select dev 2>&1 | grep -q "dev"; then + echo "✅ Stack selected successfully" +else + echo "⚠️ Stack operation completed (check output above)" +fi + +echo "" +echo "7. Testing Pulumi preview (dry run)..." +echo " This will attempt to preview the infrastructure import..." +if uv run pulumi preview --non-interactive 2>&1 | head -20; then + echo "" + echo "✅ Preview completed" +else + echo "" + echo "⚠️ Preview completed with warnings (see above)" +fi + +echo "" +echo "=== Setup Test Complete ===" +echo "" +echo "Next steps:" +echo "1. Review the preview output above" +echo "2. Run 'direnv allow' to enable automatic credential loading" +echo "3. Run 'direnv exec . uv run pulumi up' to import resources" diff --git a/pulumi/igenomes/uv.lock b/pulumi/igenomes/uv.lock new file mode 100644 index 00000000..89e209a3 --- /dev/null +++ b/pulumi/igenomes/uv.lock @@ -0,0 +1,474 @@ +version = 1 +requires-python = ">=3.11" + +[[package]] +name = "arpeggio" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/58/ba011f3cf8291804ce80f9d81289ac15f0319a27f9d7e3c124aa5e4981cc/Arpeggio-2.0.3.tar.gz", hash = "sha256:9e85ad35cfc6c938676817c7ae9a1000a7c72a34c71db0c687136c460d12b85e", size = 766566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/4d/53b8186b41842f7a5e971b1d1c28e678364dcf841e4170f5d14d38ac1e2a/Arpeggio-2.0.3-py2.py3-none-any.whl", hash = "sha256:9374d9c531b62018b787635f37fd81c9a6ee69ef2d28c5db3cd18791b1f7db2f", size = 54656 }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615 }, +] + +[[package]] +name = "black" +version = "25.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/f4/7531d4a336d2d4ac6cc101662184c8e7d068b548d35d874415ed9f4116ef/black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa", size = 1698727 }, + { url = "https://files.pythonhosted.org/packages/28/f9/66f26bfbbf84b949cc77a41a43e138d83b109502cd9c52dfc94070ca51f2/black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d", size = 1555679 }, + { url = "https://files.pythonhosted.org/packages/bf/59/61475115906052f415f518a648a9ac679d7afbc8da1c16f8fdf68a8cebed/black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608", size = 1617453 }, + { url = "https://files.pythonhosted.org/packages/7f/5b/20fd5c884d14550c911e4fb1b0dae00d4abb60a4f3876b449c4d3a9141d5/black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f", size = 1333655 }, + { url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012 }, + { url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421 }, + { url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619 }, + { url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481 }, + { url = "https://files.pythonhosted.org/packages/48/99/3acfea65f5e79f45472c45f87ec13037b506522719cd9d4ac86484ff51ac/black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175", size = 1742165 }, + { url = "https://files.pythonhosted.org/packages/3a/18/799285282c8236a79f25d590f0222dbd6850e14b060dfaa3e720241fd772/black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f", size = 1581259 }, + { url = "https://files.pythonhosted.org/packages/f1/ce/883ec4b6303acdeca93ee06b7622f1fa383c6b3765294824165d49b1a86b/black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831", size = 1655583 }, + { url = "https://files.pythonhosted.org/packages/21/17/5c253aa80a0639ccc427a5c7144534b661505ae2b5a10b77ebe13fa25334/black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357", size = 1343428 }, + { url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363 }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "debugpy" +version = "1.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/53/3af72b5c159278c4a0cf4cffa518675a0e73bdb7d1cac0239b815502d2ce/debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840", size = 2207154 }, + { url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322 }, + { url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078 }, + { url = "https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4", size = 5179011 }, + { url = "https://files.pythonhosted.org/packages/08/2b/9d8e65beb2751876c82e1aceb32f328c43ec872711fa80257c7674f45650/debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d", size = 2549522 }, + { url = "https://files.pythonhosted.org/packages/b4/78/eb0d77f02971c05fca0eb7465b18058ba84bd957062f5eec82f941ac792a/debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc", size = 4309417 }, + { url = "https://files.pythonhosted.org/packages/37/42/c40f1d8cc1fed1e75ea54298a382395b8b937d923fcf41ab0797a554f555/debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf", size = 5277130 }, + { url = "https://files.pythonhosted.org/packages/72/22/84263b205baad32b81b36eac076de0cdbe09fe2d0637f5b32243dc7c925b/debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464", size = 5319053 }, + { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386 }, + { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100 }, + { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002 }, + { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047 }, + { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899 }, + { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254 }, + { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203 }, + { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493 }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210 }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668 }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567 }, + { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017 }, + { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027 }, + { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913 }, + { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417 }, + { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683 }, + { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109 }, + { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676 }, + { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688 }, + { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315 }, + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718 }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627 }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167 }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267 }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963 }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484 }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777 }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014 }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750 }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003 }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716 }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522 }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558 }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990 }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387 }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668 }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928 }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983 }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727 }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799 }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417 }, + { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219 }, + { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826 }, + { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550 }, + { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564 }, + { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236 }, + { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795 }, + { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214 }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961 }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462 }, +] + +[[package]] +name = "igenomes" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pulumi" }, + { name = "pulumi-aws" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.5.0" }, + { name = "pulumi", specifier = ">=3.100.0,<4.0.0" }, + { name = "pulumi-aws", specifier = ">=6.0.0,<7.0.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198 }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879 }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292 }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750 }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827 }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983 }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273 }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910 }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585 }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562 }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296 }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828 }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728 }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758 }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342 }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709 }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806 }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262 }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775 }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852 }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242 }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683 }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749 }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959 }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "parver" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arpeggio" }, + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/e5/1c774688a90f0b76e872e30f6f1ba3f5e14056cd0d96a684047d4a986226/parver-0.5.tar.gz", hash = "sha256:b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777", size = 26908 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/4c/f98024021bef4d44dce3613feebd702c7ad8883f777ff8488384c59e9774/parver-0.5-py3-none-any.whl", hash = "sha256:2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2", size = 15172 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "pip" +version = "25.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622 }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963 }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818 }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091 }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824 }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942 }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823 }, +] + +[[package]] +name = "pulumi" +version = "3.204.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "debugpy" }, + { name = "dill" }, + { name = "grpcio" }, + { name = "pip" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "semver" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/5c/e1bb5294eddf966acab0c2b7dbf4b09a68d6e0073d52adb2bfb5d6feb8ec/pulumi-3.204.0-py3-none-any.whl", hash = "sha256:7fef66ce43535cb9ca2f38bb189fd4053eeacd32765f12504cb5f162df0720ff", size = 383508 }, +] + +[[package]] +name = "pulumi-aws" +version = "6.83.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/c4/9b94714424dc5eb15e19628f56afa17dc0370b86286cc693c2e7cca4e81b/pulumi_aws-6.83.1.tar.gz", hash = "sha256:056e49765c9581d2480117464b6774dd1e3ddb1c397b3bab0853d7dc070e7882", size = 7834305 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3a/9c3ba98de1f42800b86e3098be587f659c6fa62891eb7d7714d31f08339d/pulumi_aws-6.83.1-py3-none-any.whl", hash = "sha256:c6be888f3cf06e1fb44ae40b9f80ace33794d0c32bd52971731e4d7016c6d7c5", size = 10608061 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 }, +] + +[[package]] +name = "pytokens" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/c2/dbadcdddb412a267585459142bfd7cc241e6276db69339353ae6e241ab2b/pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43", size = 15368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/5a/c269ea6b348b6f2c32686635df89f32dbe05df1088dd4579302a6f8f99af/pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8", size = 12038 }, +] + +[[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 } +wheels = [ + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +] + +[[package]] +name = "ruff" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/34/8218a19b2055b80601e8fd201ec723c74c7fe1ca06d525a43ed07b6d8e85/ruff-0.14.2.tar.gz", hash = "sha256:98da787668f239313d9c902ca7c523fe11b8ec3f39345553a51b25abc4629c96", size = 5539663 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/dd/23eb2db5ad9acae7c845700493b72d3ae214dce0b226f27df89216110f2b/ruff-0.14.2-py3-none-linux_armv6l.whl", hash = "sha256:7cbe4e593505bdec5884c2d0a4d791a90301bc23e49a6b1eb642dd85ef9c64f1", size = 12533390 }, + { url = "https://files.pythonhosted.org/packages/5a/8c/5f9acff43ddcf3f85130d0146d0477e28ccecc495f9f684f8f7119b74c0d/ruff-0.14.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8d54b561729cee92f8d89c316ad7a3f9705533f5903b042399b6ae0ddfc62e11", size = 12887187 }, + { url = "https://files.pythonhosted.org/packages/99/fa/047646491479074029665022e9f3dc6f0515797f40a4b6014ea8474c539d/ruff-0.14.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c8753dfa44ebb2cde10ce5b4d2ef55a41fb9d9b16732a2c5df64620dbda44a3", size = 11925177 }, + { url = "https://files.pythonhosted.org/packages/15/8b/c44cf7fe6e59ab24a9d939493a11030b503bdc2a16622cede8b7b1df0114/ruff-0.14.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0bbeffb8d9f4fccf7b5198d566d0bad99a9cb622f1fc3467af96cb8773c9e3", size = 12358285 }, + { url = "https://files.pythonhosted.org/packages/45/01/47701b26254267ef40369aea3acb62a7b23e921c27372d127e0f3af48092/ruff-0.14.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7047f0c5a713a401e43a88d36843d9c83a19c584e63d664474675620aaa634a8", size = 12303832 }, + { url = "https://files.pythonhosted.org/packages/2d/5c/ae7244ca4fbdf2bee9d6405dcd5bc6ae51ee1df66eb7a9884b77b8af856d/ruff-0.14.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bf8d2f9aa1602599217d82e8e0af7fd33e5878c4d98f37906b7c93f46f9a839", size = 13036995 }, + { url = "https://files.pythonhosted.org/packages/27/4c/0860a79ce6fd4c709ac01173f76f929d53f59748d0dcdd662519835dae43/ruff-0.14.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1c505b389e19c57a317cf4b42db824e2fca96ffb3d86766c1c9f8b96d32048a7", size = 14512649 }, + { url = "https://files.pythonhosted.org/packages/7f/7f/d365de998069720a3abfc250ddd876fc4b81a403a766c74ff9bde15b5378/ruff-0.14.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a307fc45ebd887b3f26b36d9326bb70bf69b01561950cdcc6c0bdf7bb8e0f7cc", size = 14088182 }, + { url = "https://files.pythonhosted.org/packages/6c/ea/d8e3e6b209162000a7be1faa41b0a0c16a133010311edc3329753cc6596a/ruff-0.14.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61ae91a32c853172f832c2f40bd05fd69f491db7289fb85a9b941ebdd549781a", size = 13599516 }, + { url = "https://files.pythonhosted.org/packages/fa/ea/c7810322086db68989fb20a8d5221dd3b79e49e396b01badca07b433ab45/ruff-0.14.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1967e40286f63ee23c615e8e7e98098dedc7301568bd88991f6e544d8ae096", size = 13272690 }, + { url = "https://files.pythonhosted.org/packages/a9/39/10b05acf8c45786ef501d454e00937e1b97964f846bf28883d1f9619928a/ruff-0.14.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2877f02119cdebf52a632d743a2e302dea422bfae152ebe2f193d3285a3a65df", size = 13496497 }, + { url = "https://files.pythonhosted.org/packages/59/a1/1f25f8301e13751c30895092485fada29076e5e14264bdacc37202e85d24/ruff-0.14.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e681c5bc777de5af898decdcb6ba3321d0d466f4cb43c3e7cc2c3b4e7b843a05", size = 12266116 }, + { url = "https://files.pythonhosted.org/packages/5c/fa/0029bfc9ce16ae78164e6923ef392e5f173b793b26cc39aa1d8b366cf9dc/ruff-0.14.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e21be42d72e224736f0c992cdb9959a2fa53c7e943b97ef5d081e13170e3ffc5", size = 12281345 }, + { url = "https://files.pythonhosted.org/packages/a5/ab/ece7baa3c0f29b7683be868c024f0838770c16607bea6852e46b202f1ff6/ruff-0.14.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8264016f6f209fac16262882dbebf3f8be1629777cf0f37e7aff071b3e9b92e", size = 12629296 }, + { url = "https://files.pythonhosted.org/packages/a4/7f/638f54b43f3d4e48c6a68062794e5b367ddac778051806b9e235dfb7aa81/ruff-0.14.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ca36b4cb4db3067a3b24444463ceea5565ea78b95fe9a07ca7cb7fd16948770", size = 13371610 }, + { url = "https://files.pythonhosted.org/packages/8d/35/3654a973ebe5b32e1fd4a08ed2d46755af7267da7ac710d97420d7b8657d/ruff-0.14.2-py3-none-win32.whl", hash = "sha256:41775927d287685e08f48d8eb3f765625ab0b7042cc9377e20e64f4eb0056ee9", size = 12415318 }, + { url = "https://files.pythonhosted.org/packages/71/30/3758bcf9e0b6a4193a6f51abf84254aba00887dfa8c20aba18aa366c5f57/ruff-0.14.2-py3-none-win_amd64.whl", hash = "sha256:0df3424aa5c3c08b34ed8ce099df1021e3adaca6e90229273496b839e5a7e1af", size = 13565279 }, + { url = "https://files.pythonhosted.org/packages/2e/5d/aa883766f8ef9ffbe6aa24f7192fb71632f31a30e77eb39aa2b0dc4290ac/ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a", size = 12554956 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +]