first version
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
name: Python Code Quality
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
python-code-quality:
|
||||||
|
uses: brian/CI-templates/.gitea/workflows/python/[email protected]
|
||||||
|
with:
|
||||||
|
python-version: ${{ vars.PYTHON_VERSION }}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
name: Python Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
python-test:
|
||||||
|
uses: brian/CI-templates/.gitea/workflows/python/[email protected]
|
||||||
|
with:
|
||||||
|
python-version: ${{ vars.PYTHON_VERSION }}
|
||||||
|
coverage-package: "elprisenligenu"
|
||||||
|
api-url: ${{ vars.API_URL }}
|
||||||
|
secrets:
|
||||||
|
CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }}
|
||||||
@@ -187,4 +187,3 @@ ipython_config.py
|
|||||||
|
|
||||||
# Remove previous ipynb_checkpoints
|
# Remove previous ipynb_checkpoints
|
||||||
# git rm -r .ipynb_checkpoints/
|
# git rm -r .ipynb_checkpoints/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
repos:
|
||||||
|
# General repository hygiene hooks
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: debug-statements
|
||||||
|
- id: name-tests-test
|
||||||
|
- id: check-merge-conflict
|
||||||
|
|
||||||
|
# Python linting and formatting with Ruff
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.12.8
|
||||||
|
hooks:
|
||||||
|
- id: ruff-check
|
||||||
|
args: [--fix]
|
||||||
|
- id: ruff-format
|
||||||
|
|
||||||
|
# Static type checking with mypy
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.8.0
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
|
||||||
|
# Python syntax modernization with pyupgrade
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v3.20.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: ["--py312-plus"]
|
||||||
|
|
||||||
|
# Formatting for Markdown, JSON, and YAML with Prettier
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v3.1.0
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
files: "\\.(md|json|yaml|yml)$"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -1,3 +1,293 @@
|
|||||||
# python-encrypt-code
|
# Python Encrypt Code
|
||||||
|
|
||||||
Python-based CLI tool to encrypt and decrypt a python module.
|
[](https://www.python.org/downloads/)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
> _A tool that integrates seamlessly with modern CI and Dockerized edge deployments._
|
||||||
|
|
||||||
|
A powerful Python CLI tool for encrypting and decrypting Python modules, enabling secure distribution and execution of Python code. Perfect for protecting intellectual property, distributing commercial Python applications, or creating secure deployment packages.
|
||||||
|
|
||||||
|
> ⚠️ **SECURITY DISCLAIMER**: This tool is currently in development and **not ready for production use**. In its current version, the tool writes decrypted files to temporary directories on disk during execution, which poses a security risk as sensitive code may remain accessible in the filesystem. This security vulnerability will be addressed in a future version by implementing in-memory execution. Use at your own risk for development and testing purposes only.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🔐 **Strong Encryption**: AES-256 encryption using the `cryptography` library
|
||||||
|
- 📦 **Module Packaging**: Compress and encrypt entire Python packages into a single encrypted file
|
||||||
|
- 🚀 **Direct Execution**: Run encrypted Python modules without extracting to disk (coming in a later version)
|
||||||
|
- 🔑 **Secure Password Generation**: Built-in cryptographically secure password generator
|
||||||
|
- 🛠️ **Developer Friendly**: Full type annotations and comprehensive test coverage
|
||||||
|
- 📁 **Preserve Structure**: Maintains package hierarchy and imports when decrypting
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Using uv (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.gt-proj.com/brian/python-encrypt-code.git
|
||||||
|
cd python-encrypt-code
|
||||||
|
uv install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using pip
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.gt-proj.com/brian/python-encrypt-code.git
|
||||||
|
cd python-encrypt-code
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Generate a Secure Password
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code generate-password
|
||||||
|
# Output: Wlg2aFQ2dWhkX0U0TkxOcXBHY09acTR1ZVo5UlNPcWxSamVxOTI5a08tdz0=
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Encrypt a Python Module
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MODULE_PATH=./src/my_package
|
||||||
|
export ENCRYPTED_FILE=my_package.pec
|
||||||
|
export PASSWORD=your-secure-password
|
||||||
|
|
||||||
|
uv run python-encrypt-code encrypt ${MODULE_PATH} -o ${ENCRYPTED_FILE} -p ${PASSWORD}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Decrypt and Extract
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code decrypt ${ENCRYPTED_FILE} -o ./decrypted -p ${PASSWORD}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run Encrypted Module Directly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code run-insecure ${ENCRYPTED_FILE} -p ${PASSWORD} --script main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### `generate-password`
|
||||||
|
|
||||||
|
Generate a cryptographically secure password for encryption.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code generate-password
|
||||||
|
```
|
||||||
|
|
||||||
|
### `encrypt`
|
||||||
|
|
||||||
|
Encrypt a Python module or package directory.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code encrypt <source_path> -o <output.pec> -p <password> [-kv <key>=<value>]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
- `source_path`: Directory containing Python files to encrypt
|
||||||
|
- `-o, --output`: Output path for encrypted file
|
||||||
|
- `-p, --password`: Password for encryption
|
||||||
|
- `-kv, --key-value`: Key-value pair to embed in the encrypted file
|
||||||
|
|
||||||
|
### `decrypt`
|
||||||
|
|
||||||
|
Decrypt a file and extract contents to disk.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code decrypt <encrypted_file> -o <output_dir> -p <password> [-kv <key>=<value>]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
- `encrypted.pec`: Path to encrypted file
|
||||||
|
- `-o, --output`: Directory to extract decrypted files
|
||||||
|
- `-p, --password`: Password for decryption
|
||||||
|
- `-kv, --key-value`: Key-value pair matching what is embedded in the encrypted file
|
||||||
|
|
||||||
|
### `run-insecure`
|
||||||
|
|
||||||
|
Decrypt and execute a Python script from an encrypted package.
|
||||||
|
**N.B. this writes decrypted files to a temporary folder on the disk before executing!**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python-encrypt-code run-insecure <encrypted.pec> -p <password> --script <script_name> [-kv <key>=<value>]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
- `encrypted.pec`: Path to encrypted file
|
||||||
|
- `-p, --password`: Password for decryption
|
||||||
|
- `-s, --script`: Python script to execute (default: `main.py`)
|
||||||
|
- `-kv, --key-value`: Key-value pair matching what is embedded in the encrypted file
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
### 1. Protecting Commercial Python Applications
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Encrypt your entire application
|
||||||
|
export PASSWORD=$(uv run python-encrypt-code generate-password)
|
||||||
|
uv run python-encrypt-code encrypt ./my_app -o my_app.pec -p ${PASSWORD}
|
||||||
|
|
||||||
|
# Distribute the .pec file and password to customers
|
||||||
|
# They can run it without seeing the source code
|
||||||
|
uv run python-encrypt-code run-insecure ./my_app.pec -p ${PASSWORD} --script main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Secure Deployment Packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create encrypted deployment package
|
||||||
|
uv run python-encrypt-code encrypt ./production_code -o deployment.pec -p $DEPLOY_PASSWORD
|
||||||
|
|
||||||
|
# Deploy and run on target server
|
||||||
|
uv run python-encrypt-code run-insecure deployment.pec -p $DEPLOY_PASSWORD --script startup.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Educational Content Protection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Protect course materials or assignments
|
||||||
|
uv run python-encrypt-code encrypt ./course_solutions -o solutions.pec -p student_password
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.12+
|
||||||
|
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
|
||||||
|
|
||||||
|
### Setup Development Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/python-encrypt-code.git
|
||||||
|
cd python-encrypt-code
|
||||||
|
|
||||||
|
# Install with development dependencies
|
||||||
|
uv install --dev
|
||||||
|
|
||||||
|
# Install pre-commit hooks
|
||||||
|
uv run pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python-encrypt-code/
|
||||||
|
├── src/python_encrypt_code/ # Main package
|
||||||
|
│ ├── __main__.py # CLI entry point
|
||||||
|
│ ├── data_zipper/ # Compression utilities
|
||||||
|
│ ├── file_encrypter/ # Encryption/decryption
|
||||||
|
│ ├── password_provider/ # Password generation
|
||||||
|
│ └── module_importer/ # Dynamic module loading
|
||||||
|
├── tests/
|
||||||
|
│ ├── unit/ # Unit tests
|
||||||
|
│ ├── integration/ # Integration tests
|
||||||
|
│ └── test_data/ # Test fixtures
|
||||||
|
├── pyproject.toml # Project configuration
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
uv run pytest --cov-report=term-missing --cov=src/python_encrypt_code
|
||||||
|
|
||||||
|
# Run specific test categories
|
||||||
|
uv run pytest tests/unit/ # Unit tests only
|
||||||
|
uv run pytest tests/integration/ # Integration tests only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Type checking
|
||||||
|
uv run mypy .
|
||||||
|
|
||||||
|
# Linting and formatting
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format .
|
||||||
|
uv run prettier --write .
|
||||||
|
|
||||||
|
# Run pre-commit checks
|
||||||
|
uv run pre-commit run --all-files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Encryption Method
|
||||||
|
|
||||||
|
- **Algorithm**: AES-256-GCM (Galois/Counter Mode)
|
||||||
|
- **Key Format**: Direct 32-byte key from base64-encoded password
|
||||||
|
- **Authentication**: Built-in authentication tag prevents tampering
|
||||||
|
- **Nonce**: Random 12-byte nonce for each encryption operation
|
||||||
|
|
||||||
|
### File Format
|
||||||
|
|
||||||
|
Encrypted `.pec` files contain:
|
||||||
|
|
||||||
|
1. Nonce (12 bytes)
|
||||||
|
2. Encrypted ZIP archive with authentication tag (variable length)
|
||||||
|
|
||||||
|
### Module Loading
|
||||||
|
|
||||||
|
The `ModuleImporter` class provides sophisticated module loading capabilities:
|
||||||
|
|
||||||
|
- Preserves package structure and relative imports
|
||||||
|
- Handles `__init__.py` files correctly
|
||||||
|
- Supports running specific scripts from encrypted packages
|
||||||
|
- Temporary filesystem isolation for security
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Make your changes
|
||||||
|
4. Add tests for new functionality
|
||||||
|
5. Ensure all tests pass (`uv run pytest`)
|
||||||
|
6. Run code quality checks (`uv run pre-commit run --all-files`)
|
||||||
|
7. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
8. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
9. Open a Pull Request
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **Password Storage**: Never store passwords in plain text. Use environment variables or secure key management systems.
|
||||||
|
- **Direct Key Usage**: Uses base64-encoded 32-byte keys directly with AES-256-GCM (no key derivation).
|
||||||
|
- **Memory Safety**: Temporary decrypted files are automatically cleaned up.
|
||||||
|
- **Verification**: Authentication tags prevent tampering with encrypted files.
|
||||||
|
- **Custom Password Providers**: For production deployments, you should implement your own `PasswordProvider` class adapted to your specific security architecture (e.g., integration with HSMs, key vaults, or enterprise secret management systems). Custom implementations can collect available metadata such as user context, machine identifiers, environment variables, or request timestamps and send this information along with password requests to your secret management system for enhanced security and audit logging. The default implementation is suitable for development and testing only.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v0.1.0
|
||||||
|
|
||||||
|
- Initial release
|
||||||
|
- Basic encrypt/decrypt functionality
|
||||||
|
- CLI interface with all core commands
|
||||||
|
- Comprehensive test suite
|
||||||
|
- Type annotations and documentation
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- 🐛 **Bug Reports**: [Open an issue](https://gitea.gt-proj.com/brian/python-encrypt-code/issues)
|
||||||
|
- 💡 **Feature Requests**: [Open an issue](https://gitea.gt-proj.com/brian/python-encrypt-code/issues)
|
||||||
|
- 📖 **Documentation**: See inline documentation and type hints
|
||||||
|
- 🤝 **Contributing**: See [Contributing](#contributing) section above
|
||||||
|
|
||||||
|
## Coming Soon
|
||||||
|
|
||||||
|
- Implement key derivation: improve user friendliness as well as brute-force protection
|
||||||
|
- Support pure in-memory execution: improves security
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
[project]
|
||||||
|
name = "python-encrypt-code"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Python-based command line tool to encrypt and decrypt Python modules."
|
||||||
|
authors = [
|
||||||
|
{ name = "Brian Bjarke Jensen", email = "[email protected]" }
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"cryptography>=46.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
python-encrypt-code = "python_encrypt_code.__main__:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src/python_encrypt_code"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"mypy>=1.19.1",
|
||||||
|
"pre-commit>=4.5.1",
|
||||||
|
"pytest>=9.0.2",
|
||||||
|
"pytest-cov>=7.0.0",
|
||||||
|
"pyupgrade>=3.21.2",
|
||||||
|
"ruff>=0.14.9",
|
||||||
|
"testcontainers>=4.13.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.12"
|
||||||
|
mypy_path = "src" # tell mypy where to find the source code
|
||||||
|
ignore_missing_imports = false # do not ignore missing imports
|
||||||
|
strict = true # enable all strictness options
|
||||||
|
warn_return_any = true # nudge to use stricter types
|
||||||
|
warn_unused_configs = true # nudge to remove unused configs
|
||||||
|
disallow_untyped_defs = true # disallow untyped function definitions
|
||||||
|
disallow_incomplete_defs = true # ensure all parts of a function has type annotation
|
||||||
|
check_untyped_defs = true # dont skip untyped functions
|
||||||
|
disallow_untyped_decorators = false # allow untyped decorators to keep code more readable
|
||||||
|
no_implicit_optional = true # disallow implicit optional types leading to None-mess
|
||||||
|
warn_redundant_casts = true # nudge to use explicit type casts
|
||||||
|
warn_unused_ignores = true # nudge to remove unused ignores
|
||||||
|
warn_no_return = true # catch missing return statements
|
||||||
|
warn_unreachable = true # catch unreachable code
|
||||||
|
show_error_codes = true # show error codes in output
|
||||||
|
explicit_package_bases = true # reduce risk of import confusion
|
||||||
|
namespace_packages = true # enable namespace packages
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "testcontainers.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
"""Initialization file for the python_encrypt_code package."""
|
||||||
|
|
||||||
|
from .data_zipper import DataZipper
|
||||||
|
from .file_encrypter import FileEncrypter
|
||||||
|
from .password_provider import PasswordProvider
|
||||||
|
from .module_importer import ModuleImporter
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DataZipper",
|
||||||
|
"FileEncrypter",
|
||||||
|
"PasswordProvider",
|
||||||
|
"ModuleImporter",
|
||||||
|
]
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
"""Command-line interface for python-encrypt-code package."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from python_encrypt_code import ModuleImporter
|
||||||
|
from python_encrypt_code.utils import (
|
||||||
|
generate_password,
|
||||||
|
encrypt_folder,
|
||||||
|
decrypt_to_disk,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main CLI function."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Encrypt and run folders securely", prog="python-encrypt-code"
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
||||||
|
|
||||||
|
# Generate password command
|
||||||
|
_ = subparsers.add_parser(
|
||||||
|
name="generate-password",
|
||||||
|
help="Generate a secure password",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Encrypt command
|
||||||
|
encrypt_parser = subparsers.add_parser(
|
||||||
|
name="encrypt",
|
||||||
|
help="Encrypt a folder",
|
||||||
|
)
|
||||||
|
encrypt_parser.add_argument(
|
||||||
|
"folder",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the folder to encrypt",
|
||||||
|
)
|
||||||
|
encrypt_parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
required=True,
|
||||||
|
help="Path where the encrypted file will be saved",
|
||||||
|
)
|
||||||
|
encrypt_parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--password",
|
||||||
|
type=str,
|
||||||
|
help="Password to use for encryption",
|
||||||
|
)
|
||||||
|
encrypt_parser.add_argument(
|
||||||
|
"-aad",
|
||||||
|
"--additional-authenticated-data",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="JSON-string with optional metadata for encrypted file",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Decrypt command
|
||||||
|
decrypt_parser = subparsers.add_parser(
|
||||||
|
"decrypt",
|
||||||
|
help="Decrypt a file to disk",
|
||||||
|
)
|
||||||
|
decrypt_parser.add_argument(
|
||||||
|
"file",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the encrypted file",
|
||||||
|
)
|
||||||
|
decrypt_parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
required=True,
|
||||||
|
help="Path where the decrypted folder will be saved",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run insecure command
|
||||||
|
decrypt_run_parser = subparsers.add_parser(
|
||||||
|
"run-insecure",
|
||||||
|
help="Decrypt to temporary folder and run a script from an encrypted file",
|
||||||
|
)
|
||||||
|
decrypt_run_parser.add_argument(
|
||||||
|
"file",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the encrypted file",
|
||||||
|
)
|
||||||
|
decrypt_run_parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--script",
|
||||||
|
type=str,
|
||||||
|
default="main.py",
|
||||||
|
help="Name of the script to run (default: main.py)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
run_parser = subparsers.add_parser(
|
||||||
|
"run", help="Decrypt to memory and run a script from an encrypted file"
|
||||||
|
)
|
||||||
|
run_parser.add_argument("file", type=Path, help="Path to the encrypted file")
|
||||||
|
run_parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--script",
|
||||||
|
type=str,
|
||||||
|
default="main.py",
|
||||||
|
help="Name of the script to run (default: main.py)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.command:
|
||||||
|
parser.print_help()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.command == "generate-password":
|
||||||
|
generate_password()
|
||||||
|
|
||||||
|
elif args.command == "encrypt":
|
||||||
|
encrypt_folder(
|
||||||
|
folder_path=args.folder,
|
||||||
|
output_path=args.output,
|
||||||
|
password=args.password,
|
||||||
|
aad=args.additional_authenticated_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif args.command == "decrypt":
|
||||||
|
decrypt_to_disk(
|
||||||
|
encrypted_file_path=args.file,
|
||||||
|
output_path=args.output,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif args.command == "run-insecure":
|
||||||
|
# Create temporary directory for decrypted files
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
temp_path = Path(temp_dir)
|
||||||
|
|
||||||
|
decrypt_to_disk(
|
||||||
|
encrypted_file_path=args.file,
|
||||||
|
output_path=temp_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Import and run the specified module
|
||||||
|
ModuleImporter.import_module_from_disk(
|
||||||
|
module_path=temp_path,
|
||||||
|
main_module=args.script,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif args.command == "run":
|
||||||
|
raise NotImplementedError("The 'run' command is not implemented yet.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""Data zipper module."""
|
||||||
|
|
||||||
|
from .data_zipper import DataZipper
|
||||||
|
|
||||||
|
__all__ = ["DataZipper"]
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
"""Definition of DataZipper class."""
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from .data_zipper_interface import DataZipperInterface
|
||||||
|
|
||||||
|
|
||||||
|
class DataZipper(DataZipperInterface):
|
||||||
|
"""Class to handle zipping of data folders."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def zip_data(
|
||||||
|
input_path: Path,
|
||||||
|
output_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Zips the contents of a folder into a zip file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder_path (Path): The path to the folder to be zipped.
|
||||||
|
output_path (Path): The path where the zip file will be saved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check inputs
|
||||||
|
if not isinstance(input_path, Path):
|
||||||
|
raise TypeError("input_path must be a Path object")
|
||||||
|
if not input_path.exists():
|
||||||
|
raise FileNotFoundError(f"input_path {input_path} does not exist")
|
||||||
|
if not input_path.is_dir():
|
||||||
|
raise ValueError("input_path must be a directory")
|
||||||
|
if not isinstance(output_path, Path):
|
||||||
|
raise TypeError("output_path must be a Path object")
|
||||||
|
|
||||||
|
# Zip the folder
|
||||||
|
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||||
|
for root, _, files in os.walk(input_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = Path(root) / file
|
||||||
|
zipf.write(file_path, arcname=file_path.relative_to(input_path))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unzip_data(
|
||||||
|
input_data: BytesIO,
|
||||||
|
) -> Dict[str, BytesIO]:
|
||||||
|
"""Unzips the data of a zip file into memory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_data (BytesIO): The data of the zip file to be unzipped.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, BytesIO]: Dictionary mapping file names to BytesIO objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check inputs
|
||||||
|
if not isinstance(input_data, BytesIO):
|
||||||
|
raise TypeError("input_data must be a BytesIO object")
|
||||||
|
|
||||||
|
if input_data.getbuffer().nbytes == 0:
|
||||||
|
raise ValueError("input_data must not be empty")
|
||||||
|
|
||||||
|
# Unzip the file into memory
|
||||||
|
extracted_files = {}
|
||||||
|
with zipfile.ZipFile(input_data, "r") as zipf:
|
||||||
|
for file_info in zipf.filelist:
|
||||||
|
if not file_info.is_dir(): # Skip directories
|
||||||
|
file_content = zipf.read(file_info.filename)
|
||||||
|
extracted_files[file_info.filename] = BytesIO(file_content)
|
||||||
|
|
||||||
|
return extracted_files
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unzip_data_to_disk(
|
||||||
|
input_data: BytesIO,
|
||||||
|
output_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Unzips the data of a zip file to disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_data (BytesIO): The data of the zip file to be unzipped.
|
||||||
|
output_path (Path): The path where the unzipped files will be saved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check inputs
|
||||||
|
if not isinstance(input_data, BytesIO):
|
||||||
|
raise TypeError("input_data must be a BytesIO object")
|
||||||
|
|
||||||
|
if not isinstance(output_path, Path):
|
||||||
|
raise TypeError("output_path must be a Path object")
|
||||||
|
|
||||||
|
if input_data.getbuffer().nbytes == 0:
|
||||||
|
raise ValueError("input_data must not be empty")
|
||||||
|
|
||||||
|
# Unzip the file to disk
|
||||||
|
with zipfile.ZipFile(input_data, "r") as zipf:
|
||||||
|
zipf.extractall(output_path)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""Definition of DataZipperInterface."""
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Dict
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class DataZipperInterface(ABC):
|
||||||
|
"""Interface for data zipper implementations."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def zip_data(
|
||||||
|
input_path: Path,
|
||||||
|
output_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Zips data from the input path and saves it to the output path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_path (Path): The path to the data to be zipped.
|
||||||
|
output_path (Path): The path where the zipped data will be saved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def unzip_data(
|
||||||
|
input_data: BytesIO,
|
||||||
|
) -> Dict[str, BytesIO]:
|
||||||
|
"""Unzips data from the input path and saves it in memory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_data (BytesIO): The data of the zipped file.
|
||||||
|
Returns:
|
||||||
|
Dict[str, BytesIO]: A dictionary mapping file names to BytesIO objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def unzip_data_to_disk(
|
||||||
|
input_data: BytesIO,
|
||||||
|
output_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Unzips data from the input path and saves it to the output path on disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_data (BytesIO): The data of the zipped file.
|
||||||
|
output_path (Path): The path where the unzipped data will be saved.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""File encrypter module."""
|
||||||
|
|
||||||
|
from .file_encrypter import FileEncrypter
|
||||||
|
|
||||||
|
__all__ = ["FileEncrypter"]
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
"""Definiition of FileEncrypter class."""
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||||
|
|
||||||
|
from .file_encrypter_interface import FileEncrypterInterface
|
||||||
|
|
||||||
|
|
||||||
|
class FileEncrypter(FileEncrypterInterface):
|
||||||
|
"""Class to handle file encryption and decryption."""
|
||||||
|
|
||||||
|
_encoding: str = "utf-8"
|
||||||
|
_salt_length: int = 16 # Length of the salt in bytes
|
||||||
|
_nonce_length: int = 12 # Length of the nonce in bytes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def encrypt_file(
|
||||||
|
cls,
|
||||||
|
input_file_path: Path,
|
||||||
|
output_file_path: Path,
|
||||||
|
password: str,
|
||||||
|
additional_data: dict[str, str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Encrypt a file using the provided password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file_path (str): The path to the input file to be encrypted.
|
||||||
|
output_file_path (str): The path where the encrypted file will be saved.
|
||||||
|
password (str): The password used for encryption.
|
||||||
|
additional_data (dict[str, str] | None): Optional additional authenticated data (AAD).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check inputs
|
||||||
|
if not isinstance(input_file_path, Path):
|
||||||
|
raise TypeError("input_file_path must be a Path object")
|
||||||
|
if not input_file_path.exists():
|
||||||
|
raise FileNotFoundError(f"input_file_path {input_file_path} does not exist")
|
||||||
|
if not input_file_path.is_file():
|
||||||
|
raise ValueError("input_file_path must be a file")
|
||||||
|
if not isinstance(output_file_path, Path):
|
||||||
|
raise TypeError("output_file_path must be a Path object")
|
||||||
|
if not isinstance(password, str):
|
||||||
|
raise TypeError("password must be a string")
|
||||||
|
if len(password) == 0:
|
||||||
|
raise ValueError("password cannot be empty")
|
||||||
|
password_bytes = base64.urlsafe_b64decode(password.encode(cls._encoding))
|
||||||
|
if len(password_bytes) != 32:
|
||||||
|
raise ValueError("Password must decode to 32 bytes for AES-256")
|
||||||
|
if additional_data is not None and not isinstance(additional_data, dict):
|
||||||
|
raise TypeError("additional_data must be a dict if provided")
|
||||||
|
|
||||||
|
# Prepare additional authenticated data (AAD) if provided
|
||||||
|
additional_data_bytes = None
|
||||||
|
if additional_data is not None:
|
||||||
|
additional_data_bytes = json.dumps(additional_data).encode(cls._encoding)
|
||||||
|
|
||||||
|
# Generate a random Number Used Once (nonce)
|
||||||
|
nonce = os.urandom(cls._nonce_length)
|
||||||
|
|
||||||
|
# Initialize AESGCM with derived key
|
||||||
|
aesgcm = AESGCM(password_bytes)
|
||||||
|
|
||||||
|
# Read the input file
|
||||||
|
with open(input_file_path, "rb") as file:
|
||||||
|
original_data = file.read()
|
||||||
|
|
||||||
|
# Encrypt the data
|
||||||
|
encrypted_data = aesgcm.encrypt(nonce, original_data, additional_data_bytes)
|
||||||
|
|
||||||
|
# Write the salt, nonce, and encrypted data to the output file
|
||||||
|
with open(output_file_path, "wb") as file:
|
||||||
|
file.write(nonce + encrypted_data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decrypt_file(
|
||||||
|
cls,
|
||||||
|
input_file_path: Path,
|
||||||
|
password: str,
|
||||||
|
additional_data: dict[str, str] | None = None,
|
||||||
|
) -> BytesIO:
|
||||||
|
"""Decrypt a file using the provided password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file_path (str): The path to the input file to be decrypted.
|
||||||
|
password (str): The password used for decryption.
|
||||||
|
additional_data (dict[str, str] | None): Optional additional authenticated data (AAD).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BytesIO: The decrypted file data in memory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check inputs
|
||||||
|
if not isinstance(input_file_path, Path):
|
||||||
|
raise TypeError("input_file_path must be a Path object")
|
||||||
|
if not input_file_path.exists():
|
||||||
|
raise FileNotFoundError(f"input_file_path {input_file_path} does not exist")
|
||||||
|
if not input_file_path.is_file():
|
||||||
|
raise ValueError("input_file_path must be a file")
|
||||||
|
if not isinstance(password, str):
|
||||||
|
raise TypeError("password must be a string")
|
||||||
|
if len(password) == 0:
|
||||||
|
raise ValueError("password cannot be empty")
|
||||||
|
password_bytes = base64.urlsafe_b64decode(password.encode(cls._encoding))
|
||||||
|
if len(password_bytes) != 32:
|
||||||
|
raise ValueError("Password must decode to 32 bytes for AES-256")
|
||||||
|
if additional_data is not None and not isinstance(additional_data, dict):
|
||||||
|
raise TypeError("additional_data must be a dict if provided")
|
||||||
|
|
||||||
|
# Prepare additional authenticated data (AAD) if provided
|
||||||
|
additional_data_bytes = None
|
||||||
|
if additional_data is not None:
|
||||||
|
additional_data_bytes = json.dumps(additional_data).encode(cls._encoding)
|
||||||
|
|
||||||
|
# Initialize AESGCM with derived key
|
||||||
|
aesgcm = AESGCM(password_bytes)
|
||||||
|
|
||||||
|
# Read the input file
|
||||||
|
with open(input_file_path, "rb") as file:
|
||||||
|
file_data = file.read()
|
||||||
|
|
||||||
|
# Split the nonce and encrypted data
|
||||||
|
nonce = file_data[: cls._nonce_length]
|
||||||
|
encrypted_data = file_data[cls._nonce_length :]
|
||||||
|
|
||||||
|
# Decrypt the data
|
||||||
|
decrypted_data = aesgcm.decrypt(nonce, encrypted_data, additional_data_bytes)
|
||||||
|
|
||||||
|
# # Extract the salt and encrypted data
|
||||||
|
# salt = file_data[:16]
|
||||||
|
# encrypted_data = file_data[16:]
|
||||||
|
|
||||||
|
# # Derive the key from the password using PBKDF2
|
||||||
|
# kdf = PBKDF2HMAC(
|
||||||
|
# algorithm=hashes.SHA256(),
|
||||||
|
# length=32,
|
||||||
|
# salt=salt,
|
||||||
|
# iterations=480000, # OWASP recommended minimum for 2023+
|
||||||
|
# )
|
||||||
|
# key = base64.urlsafe_b64encode(kdf.derive(password.encode("utf-8")))
|
||||||
|
# fernet = Fernet(key)
|
||||||
|
|
||||||
|
# # Decrypt the data
|
||||||
|
# decrypted_data = fernet.decrypt(encrypted_data)
|
||||||
|
|
||||||
|
return BytesIO(decrypted_data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decrypt_to_disk(
|
||||||
|
cls,
|
||||||
|
input_file_path: Path,
|
||||||
|
output_file_path: Path,
|
||||||
|
password: str,
|
||||||
|
additional_data: dict[str, str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Decrypt a file using the provided password and save to disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file_path (str): The path to the input file to be decrypted.
|
||||||
|
output_file_path (str): The path where the decrypted file will be saved.
|
||||||
|
password (str): The password used for decryption.
|
||||||
|
additional_data (dict[str, str] | None): Optional additional authenticated data (AAD).
|
||||||
|
"""
|
||||||
|
|
||||||
|
decrypted_data_io = FileEncrypter.decrypt_file(
|
||||||
|
input_file_path, password, additional_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write the decrypted data to the output file
|
||||||
|
with open(output_file_path, "wb") as file:
|
||||||
|
file.write(decrypted_data_io.getbuffer())
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Definition of FileEncrypterInterface."""
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class FileEncrypterInterface(ABC):
|
||||||
|
"""Interface for file encrypters."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def encrypt_file(
|
||||||
|
cls,
|
||||||
|
input_file_path: Path,
|
||||||
|
output_file_path: Path,
|
||||||
|
password: str,
|
||||||
|
additional_data: dict[str, str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Encrypt a file using the provided password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file_path (str): The path to the input file to be encrypted.
|
||||||
|
output_file_path (str): The path where the encrypted file will be saved.
|
||||||
|
password (str): The password used for encryption.
|
||||||
|
additional_data (dict[str, str] | None): Optional additional authenticated data (AAD).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def decrypt_file(
|
||||||
|
cls,
|
||||||
|
input_file_path: Path,
|
||||||
|
password: str,
|
||||||
|
additional_data: dict[str, str] | None = None,
|
||||||
|
) -> BytesIO:
|
||||||
|
"""Decrypt a file using the provided password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file_path (str): The path to the input file to be decrypted.
|
||||||
|
password (str): The password used for decryption.
|
||||||
|
additional_data (dict[str, str] | None): Optional additional authenticated data (AAD).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BytesIO: The decrypted file data in memory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def decrypt_to_disk(
|
||||||
|
cls,
|
||||||
|
input_file_path: Path,
|
||||||
|
output_file_path: Path,
|
||||||
|
password: str,
|
||||||
|
additional_data: dict[str, str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Decrypt a file and save it to disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file_path (str): The path to the input file to be decrypted.
|
||||||
|
output_file_path (str): The path where the decrypted file will be saved.
|
||||||
|
password (str): The password used for decryption.
|
||||||
|
additional_data (dict[str, str] | None): Optional additional authenticated data (AAD).
|
||||||
|
"""
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""Module importer for dynamically loading modules from encrypted files."""
|
||||||
|
|
||||||
|
from .module_importer import ModuleImporter
|
||||||
|
|
||||||
|
__all__ = ["ModuleImporter"]
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
"""Definition of ModuleImporter class."""
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .module_importer_interface import ModuleImporterInterface
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleImporter(ModuleImporterInterface):
|
||||||
|
"""Class to handle importing Python modules from various sources."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def import_module(
|
||||||
|
cls,
|
||||||
|
module_data: dict[str, BytesIO],
|
||||||
|
main_module: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Imports a Python module from in-memory data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_data (dict[str, BytesIO]): A dictionary mapping module names to their bytecode streams.
|
||||||
|
main_module (str | None): The name of the main module to execute, if any.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("import_module is not implemented yet.")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def import_module_from_disk(
|
||||||
|
cls,
|
||||||
|
module_path: Path,
|
||||||
|
main_module: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Imports a Python module or package from a directory on disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_path (Path): The directory path containing Python modules to import.
|
||||||
|
main_module (str | None): The filename (e.g., 'main.py') to treat as the main module.
|
||||||
|
If specified, this module will have __name__ set to '__main__'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check inputs
|
||||||
|
if not isinstance(module_path, Path):
|
||||||
|
raise TypeError("module_path must be a Path object")
|
||||||
|
if not module_path.exists():
|
||||||
|
raise FileNotFoundError(f"module_path {module_path} does not exist")
|
||||||
|
if not module_path.is_dir():
|
||||||
|
raise ValueError("module_path must be a directory")
|
||||||
|
|
||||||
|
# Find all Python files in the directory and subdirectories
|
||||||
|
python_files = list(module_path.rglob("*.py"))
|
||||||
|
if not python_files:
|
||||||
|
raise ValueError(f"No Python files found in {module_path}")
|
||||||
|
|
||||||
|
# Add the module directory to sys.path so imports work
|
||||||
|
module_path_str = str(module_path)
|
||||||
|
path_added = False
|
||||||
|
if module_path_str not in sys.path:
|
||||||
|
sys.path.insert(0, module_path_str)
|
||||||
|
path_added = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Import all Python files as modules (dependencies first, main module last)
|
||||||
|
main_module_file = None
|
||||||
|
init_files = []
|
||||||
|
|
||||||
|
# First pass: Load all non-main, non-__init__ modules (dependencies)
|
||||||
|
for py_file in python_files:
|
||||||
|
relative_path = py_file.relative_to(module_path)
|
||||||
|
|
||||||
|
# Check if this file is the main module
|
||||||
|
is_main_module = main_module and relative_path.name == main_module
|
||||||
|
# Check if this is an __init__.py file
|
||||||
|
is_init_file = relative_path.name == "__init__.py"
|
||||||
|
|
||||||
|
if is_main_module:
|
||||||
|
# Store the main module file for later execution
|
||||||
|
main_module_file = py_file
|
||||||
|
continue
|
||||||
|
elif is_init_file:
|
||||||
|
# Store __init__.py files for later execution
|
||||||
|
init_files.append(py_file)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Load regular dependency modules first
|
||||||
|
cls._load_module(py_file, module_path, is_main=False)
|
||||||
|
|
||||||
|
# Second pass: Load __init__.py files (after their contents are available)
|
||||||
|
for init_file in init_files:
|
||||||
|
cls._load_module(init_file, module_path, is_main=False)
|
||||||
|
|
||||||
|
# Third pass: Load and execute the main module last (if specified)
|
||||||
|
if main_module_file:
|
||||||
|
cls._load_module(main_module_file, module_path, is_main=True)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up sys.path
|
||||||
|
if path_added and module_path_str in sys.path:
|
||||||
|
sys.path.remove(module_path_str)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_module(py_file: Path, module_path: Path, is_main: bool = False) -> None:
|
||||||
|
"""Load a single Python module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
py_file (Path): Path to the Python file to load
|
||||||
|
module_path (Path): Base path for the module directory
|
||||||
|
is_main (bool): Whether this is the main module
|
||||||
|
"""
|
||||||
|
relative_path = py_file.relative_to(module_path)
|
||||||
|
|
||||||
|
# Create module name from relative path
|
||||||
|
if len(relative_path.parts) > 1:
|
||||||
|
# Nested module: subdir/module.py -> subdir.module
|
||||||
|
parts = list(relative_path.parts[:-1]) + [relative_path.stem]
|
||||||
|
module_name = ".".join(parts)
|
||||||
|
else:
|
||||||
|
# Top-level module
|
||||||
|
module_name = relative_path.stem
|
||||||
|
|
||||||
|
# Use "__main__" as the spec name for main modules
|
||||||
|
spec_name = "__main__" if is_main else module_name
|
||||||
|
|
||||||
|
# Import the module from the file
|
||||||
|
spec = importlib.util.spec_from_file_location(spec_name, py_file)
|
||||||
|
if spec is None:
|
||||||
|
raise ImportError(f"Could not create spec for module {module_name}")
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
|
||||||
|
# Set module attributes
|
||||||
|
module.__file__ = str(py_file)
|
||||||
|
module.__package__ = (
|
||||||
|
".".join(relative_path.parts[:-1]) if len(relative_path.parts) > 1 else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute the module
|
||||||
|
if spec.loader is None:
|
||||||
|
raise ImportError(f"No loader found for module {module_name}")
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
# Add to sys.modules
|
||||||
|
if is_main:
|
||||||
|
sys.modules["__main__"] = module
|
||||||
|
sys.modules[module_name] = module # Also keep regular name for imports
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Definition of ModuleImporterInterface."""
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleImporterInterface(ABC):
|
||||||
|
"""Interface for module importer implementations."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def import_module(
|
||||||
|
cls,
|
||||||
|
module_data: dict[str, BytesIO],
|
||||||
|
main_module: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Imports a Python module from in-memory data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_data (dict[str, BytesIO]): A dictionary mapping module names to their bytecode streams.
|
||||||
|
main_module (str | None): The name of the main module to execute, if any.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def import_module_from_disk(
|
||||||
|
cls,
|
||||||
|
module_path: Path,
|
||||||
|
main_module: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Imports a Python module from a file on disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_path (Path): The file path of the module on disk.
|
||||||
|
main_module (str | None): The name of the main module to execute, if any.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""Password provider module."""
|
||||||
|
|
||||||
|
from .password_provider import PasswordProviderExample as PasswordProvider
|
||||||
|
|
||||||
|
__all__ = ["PasswordProvider"]
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
"""Definition of PasswordProvider class."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import secrets
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
from .password_provider_interface import PasswordProviderInterface
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordProviderExample(PasswordProviderInterface):
|
||||||
|
"""Class to provide password for encryption.
|
||||||
|
Please extend this class to implement your own password storage/retrieval logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_encoding: str = "utf-8"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_password(cls) -> str:
|
||||||
|
"""Generate a new password AES-256-GCM compliant key (32 bytes, base64-url encoded).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated password.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# AES-256 requires a 32-byte key
|
||||||
|
key_bytes = secrets.token_bytes(32)
|
||||||
|
url_safe_string = base64.urlsafe_b64encode(key_bytes).decode(cls._encoding)
|
||||||
|
return url_safe_string
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_decryption_keys(cls) -> Tuple[str, dict[str, str] | None]:
|
||||||
|
"""Placeholder function to get decryption keys. Implement your own logic here.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
password: str: The password fetched from the authentication provider.
|
||||||
|
aad: dict[str, str] | None: The additional authenticated data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Simulate using metadata to authenticate and get decryption keys
|
||||||
|
password_str = cls._get_password()
|
||||||
|
|
||||||
|
# Simulate using metadata to authenticate and get AAD
|
||||||
|
aad_dict = cls._get_additional_authenticated_data()
|
||||||
|
|
||||||
|
return password_str, aad_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _collect_metadata(cls) -> dict[str, str]:
|
||||||
|
"""Placeholder function to collect metadata. Implement your own logic here.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, str]: The metadata fetched from e.g. environment variables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Simulate fetching metadata from environment variables
|
||||||
|
return {"DEPLOYED_MODULE_VERSION": "0.1.0"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_additional_authenticated_data(cls) -> dict[str, str] | None:
|
||||||
|
"""Placeholder function to get optional additional authenticated data (AAD). Implement your own logic here.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, str] | None: The additional authenticated data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Simulate collecting metadata about self to authorize against external provider
|
||||||
|
_ = cls._collect_metadata()
|
||||||
|
|
||||||
|
# Simulate fetching AAD from an external provider
|
||||||
|
aad_str = os.getenv("PEC_AAD", None)
|
||||||
|
if aad_str is None:
|
||||||
|
return None
|
||||||
|
aad_dict = {str(k): str(v) for k, v in json.loads(aad_str).items()}
|
||||||
|
|
||||||
|
return aad_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_password(cls) -> str:
|
||||||
|
"""Placeholder function to get password. Implement your own logic here.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The password fetched from the authentication provider.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Simulate collecting metadata about self to authorize against external provider
|
||||||
|
_ = cls._collect_metadata()
|
||||||
|
|
||||||
|
# Simulate fetching password from an external authentication provider
|
||||||
|
if password_str := os.getenv("PEC_PASSWORD"):
|
||||||
|
return password_str
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
"PEC_PASSWORD environment variable not set. "
|
||||||
|
"Please set it to use this PasswordProvider."
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""Definition of PasswordProviderInterface."""
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordProviderInterface(ABC):
|
||||||
|
"""Interface for password providers."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def generate_password(cls) -> str:
|
||||||
|
"""Generate a new password for encryption and decryption.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated password.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def get_decryption_keys(cls) -> Tuple[str, dict[str, str] | None]:
|
||||||
|
"""Get decryption keys for decryption.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The decryption keys.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
"""Utility functions for encryption and decryption."""
|
||||||
|
|
||||||
|
from .decrypt_to_disk import decrypt_to_disk
|
||||||
|
from .encrypt_folder import encrypt_folder
|
||||||
|
from .generate_password import generate_password
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"decrypt_to_disk",
|
||||||
|
"encrypt_folder",
|
||||||
|
"generate_password",
|
||||||
|
]
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
"""Definition of decrypt_file_to_disk function."""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from python_encrypt_code import (
|
||||||
|
DataZipper,
|
||||||
|
FileEncrypter,
|
||||||
|
PasswordProvider,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_to_disk(
|
||||||
|
encrypted_file_path: Path,
|
||||||
|
output_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Decrypt a file and save the contents to disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
encrypted_file_path: Path to the encrypted file
|
||||||
|
output_path: Path where the decrypted folder will be saved
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Validate inputs
|
||||||
|
if not encrypted_file_path.exists():
|
||||||
|
raise FileNotFoundError(f"Encrypted file not found: {encrypted_file_path}")
|
||||||
|
|
||||||
|
if not encrypted_file_path.is_file():
|
||||||
|
raise ValueError(f"Path is not a file: {encrypted_file_path}")
|
||||||
|
|
||||||
|
# Get decryption keys
|
||||||
|
password, additional_data = PasswordProvider.get_decryption_keys()
|
||||||
|
|
||||||
|
# Create temporary zip file
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_zip:
|
||||||
|
temp_zip_path = Path(temp_zip.name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Decrypt the file
|
||||||
|
print(f"Decrypting: {encrypted_file_path}")
|
||||||
|
decrypted_data = FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=encrypted_file_path,
|
||||||
|
password=password,
|
||||||
|
additional_data=additional_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 2: Extract the zip file to disk
|
||||||
|
print(f"Extracting to: {output_path}")
|
||||||
|
DataZipper.unzip_data_to_disk(decrypted_data, output_path)
|
||||||
|
|
||||||
|
print("✅ File decrypted and extracted successfully!")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up temporary zip file
|
||||||
|
if temp_zip_path.exists():
|
||||||
|
temp_zip_path.unlink()
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
"""Definition of encrypt_folder function."""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from python_encrypt_code import (
|
||||||
|
DataZipper,
|
||||||
|
FileEncrypter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_folder(
|
||||||
|
folder_path: Path,
|
||||||
|
output_path: Path,
|
||||||
|
password: str,
|
||||||
|
aad: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Encrypt a folder.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder_path (Path): Path to the folder to encrypt
|
||||||
|
output_path (Path): Path where the encrypted file will be saved
|
||||||
|
password (str): Optional password to use. If None, generates a new one.
|
||||||
|
aad (str | None): Optional JSON-formatted metadata that will be stored in with the encrypted file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Validate inputs
|
||||||
|
if not isinstance(folder_path, Path):
|
||||||
|
raise TypeError(f"folder_path must be a Path object: {folder_path}")
|
||||||
|
if not folder_path.exists():
|
||||||
|
raise FileNotFoundError(f"Folder not found: {folder_path}")
|
||||||
|
if not folder_path.is_dir():
|
||||||
|
raise ValueError(f"Path is not a directory: {folder_path}")
|
||||||
|
if not isinstance(output_path, Path):
|
||||||
|
raise TypeError(f"output_path must be a Path object: {output_path}")
|
||||||
|
if not output_path.parent.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"Output directory does not exist: {output_path.parent}"
|
||||||
|
)
|
||||||
|
if not isinstance(password, str):
|
||||||
|
raise TypeError("password must be a string")
|
||||||
|
if len(password) == 0:
|
||||||
|
raise ValueError("password cannot be an empty string")
|
||||||
|
if aad is not None and not isinstance(aad, str):
|
||||||
|
raise TypeError("aad must be a string or None")
|
||||||
|
|
||||||
|
# Convert additional_data to if provided
|
||||||
|
if isinstance(aad, str):
|
||||||
|
additional_data = dict(json.loads(aad))
|
||||||
|
else:
|
||||||
|
additional_data = None
|
||||||
|
|
||||||
|
# Create temporary zip file
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_zip:
|
||||||
|
temp_zip_path = Path(temp_zip.name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Zip the folder
|
||||||
|
print(f"Zipping folder: {folder_path}")
|
||||||
|
DataZipper.zip_data(folder_path, temp_zip_path)
|
||||||
|
|
||||||
|
# Step 2: Encrypt the zip file
|
||||||
|
print(f"Encrypting to: {output_path}")
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=temp_zip_path,
|
||||||
|
output_file_path=output_path,
|
||||||
|
password=password,
|
||||||
|
additional_data=additional_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("✅ Folder encrypted successfully!")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up temporary zip file
|
||||||
|
if temp_zip_path.exists():
|
||||||
|
temp_zip_path.unlink()
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"""Definition of generate_password function."""
|
||||||
|
|
||||||
|
from python_encrypt_code import PasswordProvider
|
||||||
|
|
||||||
|
|
||||||
|
def generate_password() -> None:
|
||||||
|
"""Generate and print a secure password."""
|
||||||
|
|
||||||
|
# Generate and print a secure password
|
||||||
|
password = PasswordProvider.generate_password()
|
||||||
|
print(password)
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
"""Definition of integration test fixtures."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def password() -> str:
|
||||||
|
"""Fixture that provides a test password.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A test password.
|
||||||
|
"""
|
||||||
|
return "sZoOOTSY32x-4nzb_6XXtmfY2tA3R6KFcHLGS1hGA30=" # Base64-encoded 32-byte key
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def wrong_password() -> str:
|
||||||
|
"""Fixture that provides a wrong test password.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A wrong test password.
|
||||||
|
"""
|
||||||
|
return "5mtlZEFgdvVyrcpF-__xoBNFuDaUjpqLOb-Y05AOdKY=" # Base64-encoded 32-byte key
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def aad() -> str:
|
||||||
|
"""Fixture that provides additional authenticated data (AAD) for tests.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: AAD JSON string.
|
||||||
|
"""
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"TEST_KEY": "TEST_VALUE",
|
||||||
|
"MODULE_VERSION": "1.0.0",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def wrong_aad() -> str:
|
||||||
|
"""Fixture that provides wrong additional authenticated data (AAD) for tests.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Wrong AAD JSON string.
|
||||||
|
"""
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"TEST_KEY": "TEST_VALUE",
|
||||||
|
"MODULE_VERSION": "1.1.0",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def tmp_dir() -> Iterator[Path]:
|
||||||
|
"""Fixture that provides a temporary directory for tests.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Path: A temporary directory path.
|
||||||
|
"""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
yield Path(tmpdirname)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def simple_python_module() -> Path:
|
||||||
|
"""Path to a simple hello world Python module."""
|
||||||
|
return Path(__file__).parent.parent / "test_data" / "simple_python_module"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def nested_python_module() -> Path:
|
||||||
|
"""Path to a complex Python module with internal dependencies."""
|
||||||
|
return Path(__file__).parent.parent / "test_data" / "nested_python_module"
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
"""Definition of integration tests for the CLI functionality."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_password_command() -> None:
|
||||||
|
"""Test the generate-password CLI command."""
|
||||||
|
generate_password_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"generate-password",
|
||||||
|
]
|
||||||
|
output = subprocess.run(
|
||||||
|
generate_password_args, check=True, capture_output=True, text=True
|
||||||
|
)
|
||||||
|
password = output.stdout.strip()
|
||||||
|
assert len(password) > 0, "Generated password should not be empty"
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_command(
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
simple_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test the encrypt CLI command."""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted.pec"
|
||||||
|
|
||||||
|
# Encrypt the simple python module
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(encrypt_args, check=True)
|
||||||
|
|
||||||
|
assert encrypted_file.exists(), "Encrypted file should be created"
|
||||||
|
|
||||||
|
# Encrypt without optional AAD
|
||||||
|
encrypted_file_no_aad = tmp_dir / "encrypted_no_aad.pec"
|
||||||
|
encrypt_args_no_aad = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file_no_aad),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
]
|
||||||
|
subprocess.run(encrypt_args_no_aad, check=True)
|
||||||
|
|
||||||
|
assert encrypted_file_no_aad.exists(), "Encrypted file should be created"
|
||||||
|
|
||||||
|
|
||||||
|
def test_decrypt_command(
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
simple_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test the decrypt CLI command."""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted.pec"
|
||||||
|
decrypted_output_dir = tmp_dir / "decrypted_output"
|
||||||
|
|
||||||
|
# Prepare an encrypted file
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(encrypt_args, check=True)
|
||||||
|
|
||||||
|
# The PasswordProviderExample reads from environment variables
|
||||||
|
# to mock getting data from external secret management systems.
|
||||||
|
env = {**os.environ}
|
||||||
|
env["PEC_PASSWORD"] = password
|
||||||
|
env["PEC_AAD"] = aad
|
||||||
|
|
||||||
|
# Decrypt the file
|
||||||
|
decrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"decrypt",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-o",
|
||||||
|
str(decrypted_output_dir),
|
||||||
|
]
|
||||||
|
subprocess.run(decrypt_args, check=True, env=env)
|
||||||
|
|
||||||
|
assert decrypted_output_dir.exists(), "Decrypted output directory should be created"
|
||||||
|
assert any(decrypted_output_dir.iterdir()), (
|
||||||
|
"Decrypted output directory should not be empty"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_insecure_command(
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
simple_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test the run-insecure CLI command."""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted_run.pec"
|
||||||
|
|
||||||
|
# Encrypt the simple python module
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(
|
||||||
|
args=encrypt_args,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted file was created
|
||||||
|
assert encrypted_file.exists()
|
||||||
|
|
||||||
|
# Store password and AAD in environment variables for the subprocesses
|
||||||
|
env = dict(**os.environ)
|
||||||
|
env["PEC_PASSWORD"] = password
|
||||||
|
env["PEC_AAD"] = aad
|
||||||
|
|
||||||
|
# Step 2: Run the encrypted module
|
||||||
|
run_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"run-insecure",
|
||||||
|
str(encrypted_file),
|
||||||
|
"--script",
|
||||||
|
"hello_world.py",
|
||||||
|
]
|
||||||
|
result = subprocess.run(
|
||||||
|
args=run_args,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify output
|
||||||
|
assert "Hello, World!" in result.stdout
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""Test the encryption of complex python modules with multiple files and imports."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_and_run_nested_module(
|
||||||
|
nested_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test encrypting and running a module with nested imports.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nested_python_module (Path): Path to the nested module directory.
|
||||||
|
tmp_dir (Path): Temporary directory for test files.
|
||||||
|
password (str): The test password.
|
||||||
|
aad (str): Additional authenticated data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted_nested.pec"
|
||||||
|
|
||||||
|
# Encrypt the folder containing the complex module
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(nested_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(
|
||||||
|
args=encrypt_args,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted file was created
|
||||||
|
assert encrypted_file.exists()
|
||||||
|
|
||||||
|
# Prepare environment for the PasswordProviderExample
|
||||||
|
env = {**os.environ}
|
||||||
|
env["PEC_PASSWORD"] = password
|
||||||
|
env["PEC_AAD"] = aad
|
||||||
|
|
||||||
|
# Run the encrypted module with environment set
|
||||||
|
run_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"run-insecure",
|
||||||
|
str(encrypted_file),
|
||||||
|
"--script",
|
||||||
|
"main.py",
|
||||||
|
]
|
||||||
|
result = subprocess.run(
|
||||||
|
args=run_args,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify nested module functionality works
|
||||||
|
assert "Nested Python Module executed successfully." in result.stderr
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
"""Test the encryption of a simple python module and its execution."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_and_run_simple_module(
|
||||||
|
simple_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test encrypting a simple hello world module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
simple_python_module (Path): Path to the simple python module.
|
||||||
|
tmp_dir (Path): Temporary directory for test files.
|
||||||
|
password (str): The test password.
|
||||||
|
aad (str): Additional authenticated data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted.pec"
|
||||||
|
|
||||||
|
# Encrypt the simple python module
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(
|
||||||
|
args=encrypt_args,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted file was created
|
||||||
|
assert encrypted_file.exists()
|
||||||
|
|
||||||
|
# Store password and AAD in environment variables
|
||||||
|
# for the PasswordProviderExample used by the subprocesses
|
||||||
|
env = {**os.environ}
|
||||||
|
env["PEC_PASSWORD"] = password
|
||||||
|
env["PEC_AAD"] = aad
|
||||||
|
|
||||||
|
# Run the encrypted module with environment set
|
||||||
|
run_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"run-insecure",
|
||||||
|
str(encrypted_file),
|
||||||
|
"--script",
|
||||||
|
"hello_world.py",
|
||||||
|
]
|
||||||
|
result = subprocess.run(
|
||||||
|
args=run_args,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify output
|
||||||
|
assert "Hello, World!" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_with_wrong_password(
|
||||||
|
simple_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
wrong_password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test encrypting a simple hello world module and attempting to run it with a wrong password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
simple_python_module (Path): Path to the simple python module.
|
||||||
|
tmp_dir (Path): Temporary directory for test files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted_wrong_password.pec"
|
||||||
|
assert password != wrong_password, "Test setup error: passwords should differ."
|
||||||
|
|
||||||
|
# Encrypt the folder containing the hello world module
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(encrypt_args, check=True)
|
||||||
|
|
||||||
|
# Verify that the encrypted file was created
|
||||||
|
assert encrypted_file.exists()
|
||||||
|
|
||||||
|
# Store wrong password and AAD in environment variables for the subprocesses
|
||||||
|
env = {**os.environ}
|
||||||
|
env["PEC_PASSWORD"] = wrong_password
|
||||||
|
env["PEC_AAD"] = aad
|
||||||
|
|
||||||
|
# Run the encrypted module with the wrong password
|
||||||
|
run_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"run-insecure",
|
||||||
|
str(encrypted_file),
|
||||||
|
"--script",
|
||||||
|
"hello_world.py",
|
||||||
|
]
|
||||||
|
result = subprocess.run(
|
||||||
|
args=run_args,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that an error message is in the output
|
||||||
|
assert "Error" in result.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_with_wrong_aad(
|
||||||
|
simple_python_module: Path,
|
||||||
|
tmp_dir: Path,
|
||||||
|
password: str,
|
||||||
|
aad: str,
|
||||||
|
wrong_aad: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test encrypting a simple hello world module and attempting to run it with wrong AAD.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
simple_python_module (Path): Path to the simple python module.
|
||||||
|
tmp_dir (Path): Temporary directory for test files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
encrypted_file = tmp_dir / "encrypted_wrong_aad.pec"
|
||||||
|
assert aad != wrong_aad, "Test setup error: AADs should differ."
|
||||||
|
|
||||||
|
# Encrypt the folder containing the hello world module
|
||||||
|
encrypt_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"encrypt",
|
||||||
|
str(simple_python_module),
|
||||||
|
"-o",
|
||||||
|
str(encrypted_file),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
"-aad",
|
||||||
|
aad,
|
||||||
|
]
|
||||||
|
subprocess.run(
|
||||||
|
args=encrypt_args,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted file was created
|
||||||
|
assert encrypted_file.exists()
|
||||||
|
|
||||||
|
# Store wrong password and AAD in environment variables for the subprocesses
|
||||||
|
env = {**os.environ}
|
||||||
|
env["PEC_PASSWORD"] = password
|
||||||
|
env["PEC_AAD"] = wrong_aad
|
||||||
|
|
||||||
|
# Run the encrypted module with the wrong AAD
|
||||||
|
run_args = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"python_encrypt_code",
|
||||||
|
"run-insecure",
|
||||||
|
str(encrypted_file),
|
||||||
|
"--script",
|
||||||
|
"hello_world.py",
|
||||||
|
]
|
||||||
|
result = subprocess.run(
|
||||||
|
args=run_args,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that an error message is in the output
|
||||||
|
assert "Error" in result.stderr
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""Module entry point for nested_python_module testing fixture."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from src import func_one # type: ignore[attr-defined]
|
||||||
|
from src import func_two # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info("Nested Python Module is running.")
|
||||||
|
result_one = func_one()
|
||||||
|
logger.info("func_one result: %s", {result_one})
|
||||||
|
result_two = func_two()
|
||||||
|
logger.info("func_two result: %s", {result_two})
|
||||||
|
logger.info("Nested Python Module executed successfully.")
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from .func_one import func_one
|
||||||
|
from .func_two import func_two
|
||||||
|
|
||||||
|
__all__ = ["func_one", "func_two"]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Definition of func_one function."""
|
||||||
|
|
||||||
|
|
||||||
|
def func_one() -> str:
|
||||||
|
"""A simple function that returns a string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A greeting string.
|
||||||
|
"""
|
||||||
|
return "Function One says Hello!"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Definition of func_two function."""
|
||||||
|
|
||||||
|
|
||||||
|
def func_two() -> str:
|
||||||
|
"""A simple function that returns a string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A greeting string.
|
||||||
|
"""
|
||||||
|
return "Function Two says Hello!"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
"""hello_world module."""
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Hello, World!")
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
"""Definition of unit test fixtures."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
from collections.abc import Iterator
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tmp_path() -> Iterator[Path]:
|
||||||
|
"""Fixture that provides a temporary directory for tests.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Path: The path to the temporary directory.
|
||||||
|
"""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
yield Path(tmpdirname)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_file() -> Iterator[Path]:
|
||||||
|
"""Fixture that creates a sample file for testing.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Path: The path to the sample file.
|
||||||
|
"""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
sample_file = Path(tmpdirname) / "sample.txt"
|
||||||
|
sample_file.write_text("This is a sample file for testing.")
|
||||||
|
yield sample_file
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def sample_folder() -> Iterator[Path]:
|
||||||
|
"""Fixture that creates a sample folder with files for testing.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Path: The path to the sample folder.
|
||||||
|
"""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
sample_dir = Path(tmpdirname)
|
||||||
|
(sample_dir / "file1.txt").write_text("This is file 1.")
|
||||||
|
(sample_dir / "file2.txt").write_text("This is file 2.")
|
||||||
|
sub_dir = sample_dir / "subdir"
|
||||||
|
sub_dir.mkdir()
|
||||||
|
(sub_dir / "file3.txt").write_text("This is file 3 in subdir.")
|
||||||
|
yield sample_dir
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def nested_python_module() -> Path:
|
||||||
|
"""Path to a complex Python module with internal dependencies."""
|
||||||
|
return Path(__file__).parent.parent / "test_data" / "nested_python_module"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def password() -> str:
|
||||||
|
"""Fixture that provides a sample password for encryption tests.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A sample password.
|
||||||
|
"""
|
||||||
|
return "sZoOOTSY32x-4nzb_6XXtmfY2tA3R6KFcHLGS1hGA30=" # Base64-encoded 32-byte key
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def wrong_password() -> str:
|
||||||
|
"""Fixture that provides a wrong test password.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A wrong test password.
|
||||||
|
"""
|
||||||
|
return "5mtlZEFgdvVyrcpF-__xoBNFuDaUjpqLOb-Y05AOdKY=" # Base64-encoded 32-byte key
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
"""Unit tests for data_zipper module."""
|
||||||
|
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from python_encrypt_code.data_zipper.data_zipper import DataZipper
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_zipper_zip_data_checks_inputs(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test that DataZipper raises errors for invalid inputs."""
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-Path input_path
|
||||||
|
try:
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path="not_a_path", # type: ignore
|
||||||
|
output_path=tmp_path / "output.zip",
|
||||||
|
)
|
||||||
|
except TypeError as e:
|
||||||
|
assert str(e) == "input_path must be a Path object"
|
||||||
|
|
||||||
|
# Test that FileNotFoundError is raised for non-existent input_path
|
||||||
|
try:
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path=tmp_path / "non_existent_directory",
|
||||||
|
output_path=tmp_path / "output.zip",
|
||||||
|
)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
assert (
|
||||||
|
str(e) == f"input_path {tmp_path / 'non_existent_directory'} does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for non-directory input_path
|
||||||
|
tmp_not_dir = tmp_path / "not_a_directory"
|
||||||
|
tmp_not_dir.write_text("This is a file, not a directory.")
|
||||||
|
try:
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path=tmp_not_dir,
|
||||||
|
output_path=tmp_path / "output.zip",
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
assert str(e) == "input_path must be a directory"
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-Path output_path
|
||||||
|
try:
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path=tmp_path,
|
||||||
|
output_path="not_a_path", # type: ignore
|
||||||
|
)
|
||||||
|
except TypeError as e:
|
||||||
|
assert str(e) == "output_path must be a Path object"
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_zipper_zips_folder(
|
||||||
|
tmp_path: Path,
|
||||||
|
sample_folder: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test that DataZipper can zip a folder correctly."""
|
||||||
|
|
||||||
|
# Define output zip file path
|
||||||
|
output_zip = tmp_path / "output.zip"
|
||||||
|
|
||||||
|
# Zip the sample folder
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path=sample_folder,
|
||||||
|
output_path=output_zip,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the zip file was created
|
||||||
|
assert output_zip.exists(), "Zip file should be created."
|
||||||
|
|
||||||
|
# Verify the contents of the zip file
|
||||||
|
with zipfile.ZipFile(output_zip, "r") as zipf:
|
||||||
|
zip_contents = zipf.namelist()
|
||||||
|
expected_files = [
|
||||||
|
"file1.txt",
|
||||||
|
"file2.txt",
|
||||||
|
"subdir/file3.txt",
|
||||||
|
]
|
||||||
|
for expected_file in expected_files:
|
||||||
|
assert expected_file in zip_contents, (
|
||||||
|
f"{expected_file} not found in zip file."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_zipper_unzip_data_checks_inputs() -> None:
|
||||||
|
"""Test that DataZipper raises errors for invalid inputs during unzip."""
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-BytesIO input_data
|
||||||
|
try:
|
||||||
|
DataZipper.unzip_data(
|
||||||
|
input_data="not_a_path", # type: ignore
|
||||||
|
)
|
||||||
|
except TypeError as e:
|
||||||
|
assert str(e) == "input_data must be a BytesIO object"
|
||||||
|
|
||||||
|
# Test that ValueError is raised for empty BytesIO input_data
|
||||||
|
empty_bytesio = BytesIO()
|
||||||
|
try:
|
||||||
|
DataZipper.unzip_data(
|
||||||
|
input_data=empty_bytesio,
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
assert str(e) == "input_data must not be empty"
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_zipper_unzips_file(
|
||||||
|
tmp_path: Path,
|
||||||
|
sample_folder: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test that DataZipper can unzip a file correctly."""
|
||||||
|
|
||||||
|
# First, create a zip file from the sample folder
|
||||||
|
output_zip = tmp_path / "output.zip"
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path=sample_folder,
|
||||||
|
output_path=output_zip,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read the zip file into a BytesIO object
|
||||||
|
with open(output_zip, "rb") as f:
|
||||||
|
zip_data = BytesIO(f.read())
|
||||||
|
|
||||||
|
# Now, unzip the created zip file
|
||||||
|
extracted_files = DataZipper.unzip_data(
|
||||||
|
input_data=zip_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the correct files were extracted
|
||||||
|
expected_files = [
|
||||||
|
"file1.txt",
|
||||||
|
"file2.txt",
|
||||||
|
"subdir/file3.txt",
|
||||||
|
]
|
||||||
|
for expected_file in expected_files:
|
||||||
|
assert expected_file in extracted_files, (
|
||||||
|
f"{expected_file} not found in extracted files."
|
||||||
|
)
|
||||||
|
# Optionally, verify the content of the extracted files
|
||||||
|
if expected_file == "file1.txt":
|
||||||
|
assert extracted_files[expected_file].getvalue() == b"This is file 1."
|
||||||
|
elif expected_file == "file2.txt":
|
||||||
|
assert extracted_files[expected_file].getvalue() == b"This is file 2."
|
||||||
|
elif expected_file == "subdir/file3.txt":
|
||||||
|
assert (
|
||||||
|
extracted_files[expected_file].getvalue()
|
||||||
|
== b"This is file 3 in subdir."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_zipper_unzips_file_to_disk(
|
||||||
|
tmp_path: Path,
|
||||||
|
sample_folder: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test that DataZipper can unzip a file to disk correctly."""
|
||||||
|
|
||||||
|
# First, create a zip file from the sample folder
|
||||||
|
output_zip = tmp_path / "output.zip"
|
||||||
|
DataZipper.zip_data(
|
||||||
|
input_path=sample_folder,
|
||||||
|
output_path=output_zip,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read the zip file into a BytesIO object
|
||||||
|
with open(output_zip, "rb") as f:
|
||||||
|
zip_data = BytesIO(f.read())
|
||||||
|
|
||||||
|
# Define output directory for unzipping
|
||||||
|
unzip_output_dir = tmp_path / "unzipped"
|
||||||
|
unzip_output_dir.mkdir()
|
||||||
|
|
||||||
|
# Unzip the created zip file to disk
|
||||||
|
DataZipper.unzip_data_to_disk(
|
||||||
|
input_data=zip_data,
|
||||||
|
output_path=unzip_output_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the correct files were extracted to disk
|
||||||
|
expected_files = [
|
||||||
|
unzip_output_dir / "file1.txt",
|
||||||
|
unzip_output_dir / "file2.txt",
|
||||||
|
unzip_output_dir / "subdir" / "file3.txt",
|
||||||
|
]
|
||||||
|
for expected_file in expected_files:
|
||||||
|
assert expected_file.exists(), f"{expected_file} should exist on disk."
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_zipper_unzip_to_disk_checks_inputs(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test that DataZipper raises errors for invalid inputs during unzip to disk."""
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-BytesIO input_data
|
||||||
|
try:
|
||||||
|
DataZipper.unzip_data_to_disk(
|
||||||
|
input_data="not_a_path", # type: ignore
|
||||||
|
output_path=tmp_path / "output_dir",
|
||||||
|
)
|
||||||
|
except TypeError as e:
|
||||||
|
assert str(e) == "input_data must be a BytesIO object"
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-Path output_path
|
||||||
|
try:
|
||||||
|
DataZipper.unzip_data_to_disk(
|
||||||
|
input_data=BytesIO(b"some data"),
|
||||||
|
output_path="not_a_path", # type: ignore
|
||||||
|
)
|
||||||
|
except TypeError as e:
|
||||||
|
assert str(e) == "output_path must be a Path object"
|
||||||
|
|
||||||
|
# Test that ValueError is raised for empty BytesIO input_data
|
||||||
|
empty_bytesio = BytesIO()
|
||||||
|
try:
|
||||||
|
DataZipper.unzip_data_to_disk(
|
||||||
|
input_data=empty_bytesio,
|
||||||
|
output_path=tmp_path / "output_dir",
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
assert str(e) == "input_data must not be empty"
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
"""Unit tests for file_encrypter module."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from io import BytesIO
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_encrypt_code.file_encrypter import FileEncrypter
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_file_checks_inputs(
|
||||||
|
sample_file: Path,
|
||||||
|
password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that FileEncrypter raises errors for invalid inputs."""
|
||||||
|
|
||||||
|
temp_dir = sample_file.parent
|
||||||
|
output_file = temp_dir / "encrypted.bin"
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-Path input_file_path
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path="not_a_path", # type: ignore
|
||||||
|
output_file_path=output_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that FileNotFoundError is raised for non-existent input_file_path
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=temp_dir / "non_existent_file.txt",
|
||||||
|
output_file_path=output_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for non-file input_file_path (directory)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=temp_dir,
|
||||||
|
output_file_path=output_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-Path output_file_path
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path="not_a_path", # type: ignore
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-string password
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=output_file,
|
||||||
|
password=123, # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for empty password
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=output_file,
|
||||||
|
password="",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for password that does not decode to 32 bytes
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=output_file,
|
||||||
|
password="short_password",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-dict additional_data
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=output_file,
|
||||||
|
password=password,
|
||||||
|
additional_data="not_a_dict", # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_file_creates_encrypted_file(
|
||||||
|
tmp_path: Path,
|
||||||
|
password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that FileEncrypter can encrypt a file correctly."""
|
||||||
|
|
||||||
|
# Create a sample file for testing
|
||||||
|
sample_file = tmp_path / "test_file.txt"
|
||||||
|
original_content = "This is a test file with some content to encrypt."
|
||||||
|
sample_file.write_text(original_content)
|
||||||
|
|
||||||
|
# Define output encrypted file path
|
||||||
|
encrypted_file = tmp_path / "encrypted.bin"
|
||||||
|
|
||||||
|
# Encrypt the file
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file,
|
||||||
|
password=password,
|
||||||
|
additional_data=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted file was created
|
||||||
|
assert encrypted_file.exists(), "Encrypted file should be created."
|
||||||
|
|
||||||
|
# Verify that the encrypted file is different from the original
|
||||||
|
encrypted_content = encrypted_file.read_bytes()
|
||||||
|
original_content_bytes = original_content.encode("utf-8")
|
||||||
|
|
||||||
|
assert encrypted_content != original_content_bytes, (
|
||||||
|
"Encrypted content should be different from original."
|
||||||
|
)
|
||||||
|
assert len(encrypted_content) > len(original_content_bytes), (
|
||||||
|
"Encrypted file should be larger (includes salt and encryption overhead)."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the file starts with a 16-byte salt
|
||||||
|
assert len(encrypted_content) >= 16, (
|
||||||
|
"Encrypted file should contain at least 16 bytes for salt."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_file_with_different_passwords_produces_different_results(
|
||||||
|
tmp_path: Path,
|
||||||
|
password: str,
|
||||||
|
sample_file: Path,
|
||||||
|
wrong_password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that encrypting the same file with different passwords produces different results."""
|
||||||
|
|
||||||
|
encrypted_file_1 = tmp_path / "encrypted1.pec"
|
||||||
|
encrypted_file_2 = tmp_path / "encrypted2.pec"
|
||||||
|
|
||||||
|
# Encrypt with first password
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file_1,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Encrypt with second password
|
||||||
|
encrypted_file_2 = tmp_path / "encrypted2.bin"
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file_2,
|
||||||
|
password=wrong_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted files are different
|
||||||
|
encrypted_content_1 = encrypted_file_1.read_bytes()
|
||||||
|
encrypted_content_2 = encrypted_file_2.read_bytes()
|
||||||
|
|
||||||
|
assert encrypted_content_1 != encrypted_content_2, (
|
||||||
|
"Different passwords should produce different encrypted content."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_encrypt_same_file_twice_produces_different_results(
|
||||||
|
tmp_path: Path,
|
||||||
|
sample_file: Path,
|
||||||
|
password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that encrypting the same file twice with the same password produces different results (due to nonce)."""
|
||||||
|
|
||||||
|
encrypted_file_1 = tmp_path / "encrypted1.pec"
|
||||||
|
encrypted_file_2 = tmp_path / "encrypted2.pec"
|
||||||
|
|
||||||
|
# Encrypt the file first time
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file_1,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Encrypt the file second time with same password
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file_2,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify that the encrypted files are different (due to different salts)
|
||||||
|
encrypted_content_1 = encrypted_file_1.read_bytes()
|
||||||
|
encrypted_content_2 = encrypted_file_2.read_bytes()
|
||||||
|
|
||||||
|
assert encrypted_content_1 != encrypted_content_2, (
|
||||||
|
"Same file encrypted twice should produce different results due to salt randomization."
|
||||||
|
)
|
||||||
|
|
||||||
|
# But the nonces (first 12 bytes) should be different
|
||||||
|
nonce_1 = encrypted_content_1[:12]
|
||||||
|
nonce_2 = encrypted_content_2[:12]
|
||||||
|
|
||||||
|
assert nonce_1 != nonce_2, "Different encryptions should use different nonces."
|
||||||
|
|
||||||
|
|
||||||
|
def test_decrypt_file_checks_inputs(
|
||||||
|
sample_file: Path,
|
||||||
|
password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that FileEncrypter raises errors for invalid inputs during decryption."""
|
||||||
|
|
||||||
|
temp_dir = sample_file.parent
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-Path input_file_path
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path="not_a_path", # type: ignore
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that FileNotFoundError is raised for non-existent input_file_path
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=temp_dir / "non_existent_file.txt",
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for non-file input_file_path (directory)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=temp_dir,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-string password
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
password=123, # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for empty password
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
password="",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that ValueError is raised for password that does not decode to 32 bytes
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
password="short_password",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that TypeError is raised for non-dict additional_data
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
password=password,
|
||||||
|
additional_data="not_a_dict", # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_decrypt_file_returns_decrypted_content(
|
||||||
|
sample_file: Path,
|
||||||
|
password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that FileEncrypter can decrypt an encrypted file correctly."""
|
||||||
|
|
||||||
|
encrypted_file = sample_file.parent / "encrypted.pec"
|
||||||
|
|
||||||
|
# Encrypt the file
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Decrypt the file
|
||||||
|
decrypted_content: BytesIO = FileEncrypter.decrypt_file(
|
||||||
|
input_file_path=encrypted_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read original content
|
||||||
|
original_content = sample_file.read_text()
|
||||||
|
|
||||||
|
# Verify that the decrypted content matches the original content
|
||||||
|
assert decrypted_content.getvalue().decode("utf-8") == original_content, (
|
||||||
|
"Decrypted content should match the original content."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_decrypt_to_disk_writes_decrypted_file(
|
||||||
|
sample_file: Path,
|
||||||
|
password: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test that FileEncrypter can decrypt an encrypted file and write to disk."""
|
||||||
|
|
||||||
|
encrypted_file = sample_file.parent / "encrypted.pec"
|
||||||
|
decrypted_file = sample_file.parent / "decrypted.txt"
|
||||||
|
|
||||||
|
# Encrypt the file
|
||||||
|
FileEncrypter.encrypt_file(
|
||||||
|
input_file_path=sample_file,
|
||||||
|
output_file_path=encrypted_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Decrypt the file to disk
|
||||||
|
FileEncrypter.decrypt_to_disk(
|
||||||
|
input_file_path=encrypted_file,
|
||||||
|
output_file_path=decrypted_file,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read original content
|
||||||
|
original_content = sample_file.read_text()
|
||||||
|
|
||||||
|
# Verify that the decrypted file matches the original content
|
||||||
|
assert decrypted_file.read_text() == original_content, (
|
||||||
|
"Decrypted file content should match the original content."
|
||||||
|
)
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
"""Unit tests for module importer functionality."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_encrypt_code.module_importer import ModuleImporter
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_importer_comprehensive_functionality(
|
||||||
|
nested_python_module: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test comprehensive module importer functionality including packages, modules, imports, and main execution."""
|
||||||
|
|
||||||
|
# Store original modules state
|
||||||
|
original_modules = sys.modules.copy()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test regular loading first
|
||||||
|
ModuleImporter.import_module_from_disk(nested_python_module)
|
||||||
|
|
||||||
|
# Verify packages were created
|
||||||
|
assert "src" in sys.modules # src package from __init__.py
|
||||||
|
assert hasattr(sys.modules["src"], "__path__"), (
|
||||||
|
"src should be a package with __path__"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify individual modules were created
|
||||||
|
assert "src.func_one" in sys.modules
|
||||||
|
assert "src.func_two" in sys.modules
|
||||||
|
assert "main" in sys.modules
|
||||||
|
|
||||||
|
# Verify module attributes are set correctly
|
||||||
|
func_one_module = sys.modules["src.func_one"]
|
||||||
|
assert func_one_module.__file__ is not None
|
||||||
|
assert func_one_module.__file__.endswith("func_one.py")
|
||||||
|
assert func_one_module.__package__ == "src"
|
||||||
|
|
||||||
|
main_module = sys.modules["main"]
|
||||||
|
assert main_module.__file__ is not None
|
||||||
|
assert main_module.__file__.endswith("main.py")
|
||||||
|
assert main_module.__package__ is None
|
||||||
|
|
||||||
|
# Test that functions are accessible (assuming func_one has a function)
|
||||||
|
if hasattr(func_one_module, "func_one"):
|
||||||
|
assert callable(func_one_module.func_one)
|
||||||
|
|
||||||
|
# Clean up for next test
|
||||||
|
modules_to_remove = [
|
||||||
|
name for name in sys.modules.keys() if name not in original_modules
|
||||||
|
]
|
||||||
|
for module_name in modules_to_remove:
|
||||||
|
del sys.modules[module_name]
|
||||||
|
|
||||||
|
# Test main module functionality
|
||||||
|
ModuleImporter.import_module_from_disk(
|
||||||
|
nested_python_module, main_module="main.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify main module has correct __name__
|
||||||
|
assert "__main__" in sys.modules
|
||||||
|
assert sys.modules["__main__"].__name__ == "__main__"
|
||||||
|
|
||||||
|
# Verify all expected modules are still loaded
|
||||||
|
assert "src" in sys.modules
|
||||||
|
assert "src.func_one" in sys.modules
|
||||||
|
assert "src.func_two" in sys.modules
|
||||||
|
assert "main" in sys.modules
|
||||||
|
|
||||||
|
# Verify src is still a package
|
||||||
|
src_module = sys.modules["src"]
|
||||||
|
assert hasattr(src_module, "__path__")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up
|
||||||
|
modules_to_remove = [
|
||||||
|
name for name in sys.modules.keys() if name not in original_modules
|
||||||
|
]
|
||||||
|
for module_name in modules_to_remove:
|
||||||
|
del sys.modules[module_name]
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_importer_handles_edge_cases(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test module importer handling of edge cases like empty files."""
|
||||||
|
|
||||||
|
# Create actual files on disk
|
||||||
|
module_dir = tmp_path / "test_module"
|
||||||
|
module_dir.mkdir()
|
||||||
|
|
||||||
|
pkg_dir = module_dir / "pkg"
|
||||||
|
pkg_dir.mkdir()
|
||||||
|
|
||||||
|
# Create empty files
|
||||||
|
(module_dir / "empty_module.py").write_text("")
|
||||||
|
(pkg_dir / "__init__.py").write_text("# Package init")
|
||||||
|
|
||||||
|
# Also test cross-module imports
|
||||||
|
src_dir = module_dir / "src"
|
||||||
|
src_dir.mkdir()
|
||||||
|
(src_dir / "math_utils.py").write_text("def square(x): return x * x")
|
||||||
|
(src_dir / "geometry.py").write_text(
|
||||||
|
"from src.math_utils import square\ndef area_square(side): return square(side)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store original modules state
|
||||||
|
original_modules = sys.modules.copy()
|
||||||
|
|
||||||
|
try:
|
||||||
|
ModuleImporter.import_module_from_disk(module_dir)
|
||||||
|
|
||||||
|
# Verify empty modules were created successfully
|
||||||
|
assert "empty_module" in sys.modules
|
||||||
|
assert "pkg.__init__" in sys.modules
|
||||||
|
|
||||||
|
# Verify cross-module functionality works
|
||||||
|
geometry_module = sys.modules["src.geometry"]
|
||||||
|
assert hasattr(geometry_module, "area_square")
|
||||||
|
assert geometry_module.area_square(5) == 25
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up
|
||||||
|
modules_to_remove = [
|
||||||
|
name for name in sys.modules.keys() if name not in original_modules
|
||||||
|
]
|
||||||
|
for module_name in modules_to_remove:
|
||||||
|
del sys.modules[module_name]
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_importer_input_validation(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test input validation for import_module_from_disk."""
|
||||||
|
|
||||||
|
# Test invalid module_path type
|
||||||
|
with pytest.raises(TypeError, match="module_path must be a Path object"):
|
||||||
|
ModuleImporter.import_module_from_disk("not_a_path") # type: ignore
|
||||||
|
|
||||||
|
# Test non-existent path
|
||||||
|
non_existent = tmp_path / "does_not_exist"
|
||||||
|
with pytest.raises(FileNotFoundError, match="does not exist"):
|
||||||
|
ModuleImporter.import_module_from_disk(non_existent)
|
||||||
|
|
||||||
|
# Test path that is not a directory
|
||||||
|
file_path = tmp_path / "file.txt"
|
||||||
|
file_path.write_text("not a directory")
|
||||||
|
with pytest.raises(ValueError, match="module_path must be a directory"):
|
||||||
|
ModuleImporter.import_module_from_disk(file_path)
|
||||||
|
|
||||||
|
# Test directory with no Python files
|
||||||
|
empty_dir = tmp_path / "empty"
|
||||||
|
empty_dir.mkdir()
|
||||||
|
with pytest.raises(ValueError, match="No Python files found"):
|
||||||
|
ModuleImporter.import_module_from_disk(empty_dir)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""Definition of unit tests for PasswordProvider class."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
|
from python_encrypt_code.password_provider import PasswordProvider
|
||||||
|
|
||||||
|
|
||||||
|
def test_password_provider_generates_password() -> None:
|
||||||
|
"""Test that PasswordProvider generates a password correctly."""
|
||||||
|
|
||||||
|
# Generate password
|
||||||
|
password = PasswordProvider.generate_password()
|
||||||
|
|
||||||
|
# Verify password is a non-empty string
|
||||||
|
assert isinstance(password, str), "Generated password should be a string."
|
||||||
|
assert len(password) > 0, "Generated password should not be empty."
|
||||||
|
|
||||||
|
# Verify password is URL-safe base64
|
||||||
|
try:
|
||||||
|
decoded_bytes = base64.urlsafe_b64decode(
|
||||||
|
password.encode(PasswordProvider._encoding)
|
||||||
|
)
|
||||||
|
print(decoded_bytes)
|
||||||
|
assert len(decoded_bytes) == 32, "Decoded password should be 32 bytes long."
|
||||||
|
except Exception as e:
|
||||||
|
assert False, f"Generated password is not valid URL-safe base64: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_password_provider_get_decryption_keys() -> None:
|
||||||
|
"""Test that PasswordProvider.get_decryption_keys retrieves the keys correctly."""
|
||||||
|
|
||||||
|
# Set environment variable for get_decryption_keys test
|
||||||
|
expected_password = PasswordProvider.generate_password()
|
||||||
|
expected_aad = PasswordProvider._collect_metadata()
|
||||||
|
os.environ["PEC_PASSWORD"] = expected_password
|
||||||
|
os.environ["PEC_AAD"] = json.dumps(expected_aad)
|
||||||
|
|
||||||
|
# Retrieve keys
|
||||||
|
password, aad = PasswordProvider.get_decryption_keys()
|
||||||
|
|
||||||
|
# Verify retrieved password
|
||||||
|
assert isinstance(password, str), "Password should be a string."
|
||||||
|
assert len(password) > 0, "Password should not be empty."
|
||||||
|
assert password == expected_password, (
|
||||||
|
"get_decryption_keys should return the password from environment variable."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify retrieved AAD
|
||||||
|
assert isinstance(aad, dict), "AAD should be a dictionary."
|
||||||
|
assert aad == expected_aad, (
|
||||||
|
"get_decryption_keys should return the AAD from environment variable."
|
||||||
|
)
|
||||||
@@ -0,0 +1,819 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2025.11.12"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfgv"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.4.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274, upload-time = "2025-12-08T13:12:52.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638, upload-time = "2025-12-08T13:12:53.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129, upload-time = "2025-12-08T13:12:54.744Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885, upload-time = "2025-12-08T13:12:56.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974, upload-time = "2025-12-08T13:12:57.718Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538, upload-time = "2025-12-08T13:12:59.254Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912, upload-time = "2025-12-08T13:13:00.604Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054, upload-time = "2025-12-08T13:13:01.892Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619, upload-time = "2025-12-08T13:13:03.236Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496, upload-time = "2025-12-08T13:13:04.511Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808, upload-time = "2025-12-08T13:13:06.422Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616, upload-time = "2025-12-08T13:13:07.95Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261, upload-time = "2025-12-08T13:13:09.581Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297, upload-time = "2025-12-08T13:13:10.977Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673, upload-time = "2025-12-08T13:13:12.562Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652, upload-time = "2025-12-08T13:13:13.909Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251, upload-time = "2025-12-08T13:13:15.553Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492, upload-time = "2025-12-08T13:13:16.849Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850, upload-time = "2025-12-08T13:13:18.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633, upload-time = "2025-12-08T13:13:19.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586, upload-time = "2025-12-08T13:13:20.883Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412, upload-time = "2025-12-08T13:13:22.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191, upload-time = "2025-12-08T13:13:23.899Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829, upload-time = "2025-12-08T13:13:25.182Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640, upload-time = "2025-12-08T13:13:26.836Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269, upload-time = "2025-12-08T13:13:28.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990, upload-time = "2025-12-08T13:13:29.463Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340, upload-time = "2025-12-08T13:13:31.524Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638, upload-time = "2025-12-08T13:13:32.965Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705, upload-time = "2025-12-08T13:13:34.378Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125, upload-time = "2025-12-08T13:13:35.73Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844, upload-time = "2025-12-08T13:13:37.69Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700, upload-time = "2025-12-08T13:13:39.525Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321, upload-time = "2025-12-08T13:13:41.172Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222, upload-time = "2025-12-08T13:13:43.282Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411, upload-time = "2025-12-08T13:13:44.72Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505, upload-time = "2025-12-08T13:13:46.332Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569, upload-time = "2025-12-08T13:13:47.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841, upload-time = "2025-12-08T13:13:49.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343, upload-time = "2025-12-08T13:13:50.811Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672, upload-time = "2025-12-08T13:13:52.284Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715, upload-time = "2025-12-08T13:13:53.791Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225, upload-time = "2025-12-08T13:13:55.274Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559, upload-time = "2025-12-08T13:13:57.161Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724, upload-time = "2025-12-08T13:13:58.692Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582, upload-time = "2025-12-08T13:14:00.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538, upload-time = "2025-12-08T13:14:02.556Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349, upload-time = "2025-12-08T13:14:04.015Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011, upload-time = "2025-12-08T13:14:05.505Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091, upload-time = "2025-12-08T13:14:07.127Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904, upload-time = "2025-12-08T13:14:08.542Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480, upload-time = "2025-12-08T13:14:10.958Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074, upload-time = "2025-12-08T13:14:13.345Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342, upload-time = "2025-12-08T13:14:15.02Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713, upload-time = "2025-12-08T13:14:16.907Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825, upload-time = "2025-12-08T13:14:18.68Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233, upload-time = "2025-12-08T13:14:20.55Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779, upload-time = "2025-12-08T13:14:22.367Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700, upload-time = "2025-12-08T13:14:24.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302, upload-time = "2025-12-08T13:14:26.068Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136, upload-time = "2025-12-08T13:14:27.604Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467, upload-time = "2025-12-08T13:14:29.09Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875, upload-time = "2025-12-08T13:14:31.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982, upload-time = "2025-12-08T13:14:33.1Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016, upload-time = "2025-12-08T13:14:34.601Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "46.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "distlib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "docker"
|
||||||
|
version = "7.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filelock"
|
||||||
|
version = "3.20.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "identify"
|
||||||
|
version = "2.6.15"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.11"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "librt"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b5/8a/071f6628363d83e803d4783e0cd24fb9c5b798164300fcfaaa47c30659c0/librt-0.7.5.tar.gz", hash = "sha256:de4221a1181fa9c8c4b5f35506ed6f298948f44003d84d2a8b9885d7e01e6cfa", size = 145868, upload-time = "2025-12-25T03:53:16.039Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/9d/cb0a296cee177c0fee7999ada1c1af7eee0e2191372058814a4ca6d2baf0/librt-0.7.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba1077c562a046208a2dc6366227b3eeae8f2c2ab4b41eaf4fd2fa28cece4203", size = 55689, upload-time = "2025-12-25T03:52:14.041Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/5c/d7de4d4228b74c5b81a3fbada157754bb29f0e1f8c38229c669a7f90422a/librt-0.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:654fdc971c76348a73af5240d8e2529265b9a7ba6321e38dd5bae7b0d4ab3abe", size = 57142, upload-time = "2025-12-25T03:52:15.336Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/b2/5da779184aae369b69f4ae84225f63741662a0fe422e91616c533895d7a4/librt-0.7.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6b7b58913d475911f6f33e8082f19dd9b120c4f4a5c911d07e395d67b81c6982", size = 165323, upload-time = "2025-12-25T03:52:16.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/40/6d5abc15ab6cc70e04c4d201bb28baffff4cfb46ab950b8e90935b162d58/librt-0.7.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e0fd344bad57026a8f4ccfaf406486c2fc991838050c2fef156170edc3b775", size = 174218, upload-time = "2025-12-25T03:52:17.518Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/d0/5239a8507e6117a3cb59ce0095bdd258bd2a93d8d4b819a506da06d8d645/librt-0.7.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46aa91813c267c3f60db75d56419b42c0c0b9748ec2c568a0e3588e543fb4233", size = 189007, upload-time = "2025-12-25T03:52:18.585Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/a4/8eed1166ffddbb01c25363e4c4e655f4bac298debe9e5a2dcfaf942438a1/librt-0.7.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ddc0ab9dbc5f9ceaf2bf7a367bf01f2697660e908f6534800e88f43590b271db", size = 183962, upload-time = "2025-12-25T03:52:19.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/83/260e60aab2f5ccba04579c5c46eb3b855e51196fde6e2bcf6742d89140a8/librt-0.7.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7a488908a470451338607650f1c064175094aedebf4a4fa37890682e30ce0b57", size = 177611, upload-time = "2025-12-25T03:52:21.18Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/36/6dcfed0df41e9695665462bab59af15b7ed2b9c668d85c7ebadd022cbb76/librt-0.7.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e47fc52602ffc374e69bf1b76536dc99f7f6dd876bd786c8213eaa3598be030a", size = 199273, upload-time = "2025-12-25T03:52:22.25Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/b7/157149c8cffae6bc4293a52e0267860cee2398cb270798d94f1c8a69b9ae/librt-0.7.5-cp312-cp312-win32.whl", hash = "sha256:cda8b025875946ffff5a9a7590bf9acde3eb02cb6200f06a2d3e691ef3d9955b", size = 43191, upload-time = "2025-12-25T03:52:23.643Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/91/197dfeb8d3bdeb0a5344d0d8b3077f183ba5e76c03f158126f6072730998/librt-0.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:b591c094afd0ffda820e931148c9e48dc31a556dc5b2b9b3cc552fa710d858e4", size = 49462, upload-time = "2025-12-25T03:52:24.637Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/ea/052a79454cc52081dfaa9a1c4c10a529f7a6a6805b2fac5805fea5b25975/librt-0.7.5-cp312-cp312-win_arm64.whl", hash = "sha256:532ddc6a8a6ca341b1cd7f4d999043e4c71a212b26fe9fd2e7f1e8bb4e873544", size = 42830, upload-time = "2025-12-25T03:52:25.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/9a/8f61e16de0ff76590af893cfb5b1aa5fa8b13e5e54433d0809c7033f59ed/librt-0.7.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b1795c4b2789b458fa290059062c2f5a297ddb28c31e704d27e161386469691a", size = 55750, upload-time = "2025-12-25T03:52:26.975Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/7c/a8a883804851a066f301e0bad22b462260b965d5c9e7fe3c5de04e6f91f8/librt-0.7.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2fcbf2e135c11f721193aa5f42ba112bb1046afafbffd407cbc81d8d735c74d0", size = 57170, upload-time = "2025-12-25T03:52:27.948Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/5d/b3b47facf5945be294cf8a835b03589f70ee0e791522f99ec6782ed738b3/librt-0.7.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c039bbf79a9a2498404d1ae7e29a6c175e63678d7a54013a97397c40aee026c5", size = 165834, upload-time = "2025-12-25T03:52:29.09Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/b6/b26910cd0a4e43e5d02aacaaea0db0d2a52e87660dca08293067ee05601a/librt-0.7.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3919c9407faeeee35430ae135e3a78acd4ecaaaa73767529e2c15ca1d73ba325", size = 174820, upload-time = "2025-12-25T03:52:30.463Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/a3/81feddd345d4c869b7a693135a462ae275f964fcbbe793d01ea56a84c2ee/librt-0.7.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26b46620e1e0e45af510d9848ea0915e7040605dd2ae94ebefb6c962cbb6f7ec", size = 189609, upload-time = "2025-12-25T03:52:31.492Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/a9/31310796ef4157d1d37648bf4a3b84555319f14cee3e9bad7bdd7bfd9a35/librt-0.7.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9bbb8facc5375476d392990dd6a71f97e4cb42e2ac66f32e860f6e47299d5e89", size = 184589, upload-time = "2025-12-25T03:52:32.59Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/22/da3900544cb0ac6ab7a2857850158a0a093b86f92b264aa6c4a4f2355ff3/librt-0.7.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e9e9c988b5ffde7be02180f864cbd17c0b0c1231c235748912ab2afa05789c25", size = 178251, upload-time = "2025-12-25T03:52:33.745Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/77/78e02609846e78b9b8c8e361753b3dbac9a07e6d5b567fe518de9e074ab0/librt-0.7.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:edf6b465306215b19dbe6c3fb63cf374a8f3e1ad77f3b4c16544b83033bbb67b", size = 199852, upload-time = "2025-12-25T03:52:34.826Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/25/05706f6b346429c951582f1b3561f4d5e1418d0d7ba1a0c181237cd77b3b/librt-0.7.5-cp313-cp313-win32.whl", hash = "sha256:060bde69c3604f694bd8ae21a780fe8be46bb3dbb863642e8dfc75c931ca8eee", size = 43250, upload-time = "2025-12-25T03:52:35.905Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/59/c38677278ac0b9ae1afc611382ef6c9ea87f52ad257bd3d8d65f0eacdc6a/librt-0.7.5-cp313-cp313-win_amd64.whl", hash = "sha256:a82d5a0ee43aeae2116d7292c77cc8038f4841830ade8aa922e098933b468b9e", size = 49421, upload-time = "2025-12-25T03:52:36.895Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/47/1d71113df4a81de5fdfbd3d7244e05d3d67e89f25455c3380ca50b92741e/librt-0.7.5-cp313-cp313-win_arm64.whl", hash = "sha256:3c98a8d0ac9e2a7cb8ff8c53e5d6e8d82bfb2839abf144fdeaaa832f2a12aa45", size = 42827, upload-time = "2025-12-25T03:52:37.856Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/ae/8635b4efdc784220f1378be640d8b1a794332f7f6ea81bb4859bf9d18aa7/librt-0.7.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9937574e6d842f359b8585903d04f5b4ab62277a091a93e02058158074dc52f2", size = 55191, upload-time = "2025-12-25T03:52:38.839Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/11/ed7ef6955dc2032af37db9b0b31cd5486a138aa792e1bb9e64f0f4950e27/librt-0.7.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5cd3afd71e9bc146203b6c8141921e738364158d4aa7cdb9a874e2505163770f", size = 56894, upload-time = "2025-12-25T03:52:39.805Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/f1/02921d4a66a1b5dcd0493b89ce76e2762b98c459fe2ad04b67b2ea6fdd39/librt-0.7.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cffa3ef0af29687455161cb446eff059bf27607f95163d6a37e27bcb37180f6", size = 163726, upload-time = "2025-12-25T03:52:40.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/87/27df46d2756fcb7a82fa7f6ca038a0c6064c3e93ba65b0b86fbf6a4f76a2/librt-0.7.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82f3f088482e2229387eadf8215c03f7726d56f69cce8c0c40f0795aebc9b361", size = 172470, upload-time = "2025-12-25T03:52:42.226Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/a9/e65a35e5d423639f4f3d8e17301ff13cc41c2ff97677fe9c361c26dbfbb7/librt-0.7.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7aa33153a5bb0bac783d2c57885889b1162823384e8313d47800a0e10d0070e", size = 186807, upload-time = "2025-12-25T03:52:43.688Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/b0/ac68aa582a996b1241773bd419823290c42a13dc9f494704a12a17ddd7b6/librt-0.7.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:265729b551a2dd329cc47b323a182fb7961af42abf21e913c9dd7d3331b2f3c2", size = 181810, upload-time = "2025-12-25T03:52:45.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/c1/03f6717677f20acd2d690813ec2bbe12a2de305f32c61479c53f7b9413bc/librt-0.7.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:168e04663e126416ba712114050f413ac306759a1791d87b7c11d4428ba75760", size = 175599, upload-time = "2025-12-25T03:52:46.177Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/d7/f976ff4c07c59b69bb5eec7e5886d43243075bbef834428124b073471c86/librt-0.7.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:553dc58987d1d853adda8aeadf4db8e29749f0b11877afcc429a9ad892818ae2", size = 196506, upload-time = "2025-12-25T03:52:47.327Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/74/004f068b8888e61b454568b5479f88018fceb14e511ac0609cccee7dd227/librt-0.7.5-cp314-cp314-win32.whl", hash = "sha256:263f4fae9eba277513357c871275b18d14de93fd49bf5e43dc60a97b81ad5eb8", size = 39747, upload-time = "2025-12-25T03:52:48.437Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/b1/ea3ec8fcf5f0a00df21f08972af77ad799604a306db58587308067d27af8/librt-0.7.5-cp314-cp314-win_amd64.whl", hash = "sha256:85f485b7471571e99fab4f44eeb327dc0e1f814ada575f3fa85e698417d8a54e", size = 45970, upload-time = "2025-12-25T03:52:49.389Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/30/5e3fb7ac4614a50fc67e6954926137d50ebc27f36419c9963a94f931f649/librt-0.7.5-cp314-cp314-win_arm64.whl", hash = "sha256:49c596cd18e90e58b7caa4d7ca7606049c1802125fcff96b8af73fa5c3870e4d", size = 39075, upload-time = "2025-12-25T03:52:50.395Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/7f/0af0a9306a06c2aabee3a790f5aa560c50ec0a486ab818a572dd3db6c851/librt-0.7.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:54d2aef0b0f5056f130981ad45081b278602ff3657fe16c88529f5058038e802", size = 57375, upload-time = "2025-12-25T03:52:51.439Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/1f/c85e510baf6572a3d6ef40c742eacedc02973ed2acdb5dba2658751d9af8/librt-0.7.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0b4791202296ad51ac09a3ff58eb49d9da8e3a4009167a6d76ac418a974e5fd4", size = 59234, upload-time = "2025-12-25T03:52:52.687Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/b1/bb6535e4250cd18b88d6b18257575a0239fa1609ebba925f55f51ae08e8e/librt-0.7.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e860909fea75baef941ee6436e0453612505883b9d0d87924d4fda27865b9a2", size = 183873, upload-time = "2025-12-25T03:52:53.705Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/49/ad4a138cca46cdaa7f0e15fa912ce3ccb4cc0d4090bfeb8ccc35766fa6d5/librt-0.7.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f02c4337bf271c4f06637f5ff254fad2238c0b8e32a3a480ebb2fc5e26f754a5", size = 194609, upload-time = "2025-12-25T03:52:54.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/2d/3b3cb933092d94bb2c1d3c9b503d8775f08d806588c19a91ee4d1495c2a8/librt-0.7.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f51ffe59f4556243d3cc82d827bde74765f594fa3ceb80ec4de0c13ccd3416", size = 206777, upload-time = "2025-12-25T03:52:55.969Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/52/6e7611d3d1347812233dabc44abca4c8065ee97b83c9790d7ecc3f782bc8/librt-0.7.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b7f080ba30601dfa3e3deed3160352273e1b9bc92e652f51103c3e9298f7899", size = 203208, upload-time = "2025-12-25T03:52:57.036Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/aa/466ae4654bd2d45903fbf180815d41e3ae8903e5a1861f319f73c960a843/librt-0.7.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb565b4219abc8ea2402e61c7ba648a62903831059ed3564fa1245cc245d58d7", size = 196698, upload-time = "2025-12-25T03:52:58.481Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/8f/424f7e4525bb26fe0d3e984d1c0810ced95e53be4fd867ad5916776e18a3/librt-0.7.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a3cfb15961e7333ea6ef033dc574af75153b5c230d5ad25fbcd55198f21e0cf", size = 217194, upload-time = "2025-12-25T03:52:59.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/33/13a4cb798a171b173f3c94db23adaf13a417130e1493933dc0df0d7fb439/librt-0.7.5-cp314-cp314t-win32.whl", hash = "sha256:118716de5ad6726332db1801bc90fa6d94194cd2e07c1a7822cebf12c496714d", size = 40282, upload-time = "2025-12-25T03:53:01.091Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/f1/62b136301796399d65dad73b580f4509bcbd347dff885a450bff08e80cb6/librt-0.7.5-cp314-cp314t-win_amd64.whl", hash = "sha256:3dd58f7ce20360c6ce0c04f7bd9081c7f9c19fc6129a3c705d0c5a35439f201d", size = 46764, upload-time = "2025-12-25T03:53:02.381Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/cb/940431d9410fda74f941f5cd7f0e5a22c63be7b0c10fa98b2b7022b48cb1/librt-0.7.5-cp314-cp314t-win_arm64.whl", hash = "sha256:08153ea537609d11f774d2bfe84af39d50d5c9ca3a4d061d946e0c9d8bce04a1", size = 39728, upload-time = "2025-12-25T03:53:03.306Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "1.19.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
|
{ name = "mypy-extensions" },
|
||||||
|
{ name = "pathspec" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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, upload-time = "2025-04-22T14:54:24.164Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nodeenv"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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, upload-time = "2023-12-10T22:30:45Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.5.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pre-commit"
|
||||||
|
version = "4.5.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cfgv" },
|
||||||
|
{ name = "identify" },
|
||||||
|
{ name = "nodeenv" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "virtualenv" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "2.23"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.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/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "coverage" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-encrypt-code"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "mypy" },
|
||||||
|
{ name = "pre-commit" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "pyupgrade" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "testcontainers" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "cryptography", specifier = ">=46.0.3" }]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "mypy", specifier = ">=1.19.1" },
|
||||||
|
{ name = "pre-commit", specifier = ">=4.5.1" },
|
||||||
|
{ name = "pytest", specifier = ">=9.0.2" },
|
||||||
|
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
||||||
|
{ name = "pyupgrade", specifier = ">=3.21.2" },
|
||||||
|
{ name = "ruff", specifier = ">=0.14.9" },
|
||||||
|
{ name = "testcontainers", specifier = ">=4.13.3" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyupgrade"
|
||||||
|
version = "3.21.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "tokenize-rt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/dc63caaeed232b1c58eae1b7a75f262d64ab8435882f696ffa9b58c0c415/pyupgrade-3.21.2.tar.gz", hash = "sha256:1a361bea39deda78d1460f65d9dd548d3a36ff8171d2482298539b9dc11c9c06", size = 45455, upload-time = "2025-11-19T00:39:48.012Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/8c/433dac11910989a90c40b10149d07ef7224232236971a562d3976790ec53/pyupgrade-3.21.2-py2.py3-none-any.whl", hash = "sha256:2ac7b95cbd176475041e4dfe8ef81298bd4654a244f957167bd68af37d52be9f", size = 62814, upload-time = "2025-11-19T00:39:46.958Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pywin32"
|
||||||
|
version = "311"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.32.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.14.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "testcontainers"
|
||||||
|
version = "4.13.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "docker" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
{ name = "wrapt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fc/b3/c272537f3ea2f312555efeb86398cc382cd07b740d5f3c730918c36e64e1/testcontainers-4.13.3.tar.gz", hash = "sha256:9d82a7052c9a53c58b69e1dc31da8e7a715e8b3ec1c4df5027561b47e2efe646", size = 79064, upload-time = "2025-11-14T05:08:47.584Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/27/c2f24b19dafa197c514abe70eda69bc031c5152c6b1f1e5b20099e2ceedd/testcontainers-4.13.3-py3-none-any.whl", hash = "sha256:063278c4805ffa6dd85e56648a9da3036939e6c0ac1001e851c9276b19b05970", size = 124784, upload-time = "2025-11-14T05:08:46.053Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokenize-rt"
|
||||||
|
version = "6.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/69/ed/8f07e893132d5051d86a553e749d5c89b2a4776eb3a579b72ed61f8559ca/tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6", size = 5476, upload-time = "2025-05-23T23:48:00.035Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44", size = 6004, upload-time = "2025-05-23T23:47:58.812Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.6.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtualenv"
|
||||||
|
version = "20.35.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "distlib" },
|
||||||
|
{ name = "filelock" },
|
||||||
|
{ name = "platformdirs" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wrapt"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/73/8cb252858dc8254baa0ce58ce382858e3a1cf616acebc497cb13374c95c6/wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c", size = 78129, upload-time = "2025-11-07T00:43:48.852Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/42/44a0db2108526ee6e17a5ab72478061158f34b08b793df251d9fbb9a7eb4/wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841", size = 61205, upload-time = "2025-11-07T00:43:50.402Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/8a/5b4b1e44b791c22046e90d9b175f9a7581a8cc7a0debbb930f81e6ae8e25/wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62", size = 61692, upload-time = "2025-11-07T00:43:51.678Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/53/3e794346c39f462bcf1f58ac0487ff9bdad02f9b6d5ee2dc84c72e0243b2/wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf", size = 121492, upload-time = "2025-11-07T00:43:55.017Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/7e/10b7b0e8841e684c8ca76b462a9091c45d62e8f2de9c4b1390b690eadf16/wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9", size = 123064, upload-time = "2025-11-07T00:43:56.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/d1/3c1e4321fc2f5ee7fd866b2d822aa89b84495f28676fd976c47327c5b6aa/wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b", size = 117403, upload-time = "2025-11-07T00:43:53.258Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/b0/d2f0a413cf201c8c2466de08414a15420a25aa83f53e647b7255cc2fab5d/wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba", size = 121500, upload-time = "2025-11-07T00:43:57.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/45/bddb11d28ca39970a41ed48a26d210505120f925918592283369219f83cc/wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684", size = 116299, upload-time = "2025-11-07T00:43:58.877Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/af/34ba6dd570ef7a534e7eec0c25e2615c355602c52aba59413411c025a0cb/wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb", size = 120622, upload-time = "2025-11-07T00:43:59.962Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/3e/693a13b4146646fb03254636f8bafd20c621955d27d65b15de07ab886187/wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9", size = 58246, upload-time = "2025-11-07T00:44:03.169Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/36/715ec5076f925a6be95f37917b66ebbeaa1372d1862c2ccd7a751574b068/wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75", size = 60492, upload-time = "2025-11-07T00:44:01.027Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/3e/62451cd7d80f65cc125f2b426b25fbb6c514bf6f7011a0c3904fc8c8df90/wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b", size = 58987, upload-time = "2025-11-07T00:44:02.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/fe/41af4c46b5e498c90fc87981ab2972fbd9f0bccda597adb99d3d3441b94b/wrapt-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9", size = 78132, upload-time = "2025-11-07T00:44:04.628Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/92/d68895a984a5ebbbfb175512b0c0aad872354a4a2484fbd5552e9f275316/wrapt-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f", size = 61211, upload-time = "2025-11-07T00:44:05.626Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/26/ba83dc5ae7cf5aa2b02364a3d9cf74374b86169906a1f3ade9a2d03cf21c/wrapt-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218", size = 61689, upload-time = "2025-11-07T00:44:06.719Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/67/d7a7c276d874e5d26738c22444d466a3a64ed541f6ef35f740dbd865bab4/wrapt-2.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9", size = 121502, upload-time = "2025-11-07T00:44:09.557Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/6b/806dbf6dd9579556aab22fc92908a876636e250f063f71548a8660382184/wrapt-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c", size = 123110, upload-time = "2025-11-07T00:44:10.64Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/08/cdbb965fbe4c02c5233d185d070cabed2ecc1f1e47662854f95d77613f57/wrapt-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db", size = 117434, upload-time = "2025-11-07T00:44:08.138Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/d1/6aae2ce39db4cb5216302fa2e9577ad74424dfbe315bd6669725569e048c/wrapt-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233", size = 121533, upload-time = "2025-11-07T00:44:12.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/35/565abf57559fbe0a9155c29879ff43ce8bd28d2ca61033a3a3dd67b70794/wrapt-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2", size = 116324, upload-time = "2025-11-07T00:44:13.28Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/e0/53ff5e76587822ee33e560ad55876d858e384158272cd9947abdd4ad42ca/wrapt-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b", size = 120627, upload-time = "2025-11-07T00:44:14.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/7b/38df30fd629fbd7612c407643c63e80e1c60bcc982e30ceeae163a9800e7/wrapt-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7", size = 58252, upload-time = "2025-11-07T00:44:17.814Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/64/d3954e836ea67c4d3ad5285e5c8fd9d362fd0a189a2db622df457b0f4f6a/wrapt-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3", size = 60500, upload-time = "2025-11-07T00:44:15.561Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/4e/3c8b99ac93527cfab7f116089db120fef16aac96e5f6cdb724ddf286086d/wrapt-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8", size = 58993, upload-time = "2025-11-07T00:44:16.65Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/f4/eff2b7d711cae20d220780b9300faa05558660afb93f2ff5db61fe725b9a/wrapt-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3", size = 82028, upload-time = "2025-11-07T00:44:18.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/67/cb945563f66fd0f61a999339460d950f4735c69f18f0a87ca586319b1778/wrapt-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1", size = 62949, upload-time = "2025-11-07T00:44:20.074Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/ca/f63e177f0bbe1e5cf5e8d9b74a286537cd709724384ff20860f8f6065904/wrapt-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d", size = 63681, upload-time = "2025-11-07T00:44:21.345Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/a1/1b88fcd21fd835dca48b556daef750952e917a2794fa20c025489e2e1f0f/wrapt-2.0.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7", size = 152696, upload-time = "2025-11-07T00:44:24.318Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/1c/d9185500c1960d9f5f77b9c0b890b7fc62282b53af7ad1b6bd779157f714/wrapt-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3", size = 158859, upload-time = "2025-11-07T00:44:25.494Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/60/5d796ed0f481ec003220c7878a1d6894652efe089853a208ea0838c13086/wrapt-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b", size = 146068, upload-time = "2025-11-07T00:44:22.81Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/f8/75282dd72f102ddbfba137e1e15ecba47b40acff32c08ae97edbf53f469e/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10", size = 155724, upload-time = "2025-11-07T00:44:26.634Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/27/fe39c51d1b344caebb4a6a9372157bdb8d25b194b3561b52c8ffc40ac7d1/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf", size = 144413, upload-time = "2025-11-07T00:44:27.939Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/2b/9f6b643fe39d4505c7bf926d7c2595b7cb4b607c8c6b500e56c6b36ac238/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e", size = 150325, upload-time = "2025-11-07T00:44:29.29Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/b6/20ffcf2558596a7f58a2e69c89597128781f0b88e124bf5a4cadc05b8139/wrapt-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c", size = 59943, upload-time = "2025-11-07T00:44:33.211Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/6a/0e56111cbb3320151eed5d3821ee1373be13e05b376ea0870711f18810c3/wrapt-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92", size = 63240, upload-time = "2025-11-07T00:44:30.935Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/54/5ab4c53ea1f7f7e5c3e7c1095db92932cc32fd62359d285486d00c2884c3/wrapt-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f", size = 60416, upload-time = "2025-11-07T00:44:32.002Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/81/d08d83c102709258e7730d3cd25befd114c60e43ef3891d7e6877971c514/wrapt-2.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1", size = 78290, upload-time = "2025-11-07T00:44:34.691Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/14/393afba2abb65677f313aa680ff0981e829626fed39b6a7e3ec807487790/wrapt-2.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55", size = 61255, upload-time = "2025-11-07T00:44:35.762Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/10/a4a1f2fba205a9462e36e708ba37e5ac95f4987a0f1f8fd23f0bf1fc3b0f/wrapt-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0", size = 61797, upload-time = "2025-11-07T00:44:37.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/db/99ba5c37cf1c4fad35349174f1e38bd8d992340afc1ff27f526729b98986/wrapt-2.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509", size = 120470, upload-time = "2025-11-07T00:44:39.425Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/3f/a1c8d2411eb826d695fc3395a431757331582907a0ec59afce8fe8712473/wrapt-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1", size = 122851, upload-time = "2025-11-07T00:44:40.582Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/8d/72c74a63f201768d6a04a8845c7976f86be6f5ff4d74996c272cefc8dafc/wrapt-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970", size = 117433, upload-time = "2025-11-07T00:44:38.313Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/5a/df37cf4042cb13b08256f8e27023e2f9b3d471d553376616591bb99bcb31/wrapt-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c", size = 121280, upload-time = "2025-11-07T00:44:41.69Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/34/40d6bc89349f9931e1186ceb3e5fbd61d307fef814f09fbbac98ada6a0c8/wrapt-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41", size = 116343, upload-time = "2025-11-07T00:44:43.013Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/66/81c3461adece09d20781dee17c2366fdf0cb8754738b521d221ca056d596/wrapt-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed", size = 119650, upload-time = "2025-11-07T00:44:44.523Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/3a/d0146db8be8761a9e388cc9cc1c312b36d583950ec91696f19bbbb44af5a/wrapt-2.0.1-cp314-cp314-win32.whl", hash = "sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0", size = 58701, upload-time = "2025-11-07T00:44:48.277Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/38/5359da9af7d64554be63e9046164bd4d8ff289a2dd365677d25ba3342c08/wrapt-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c", size = 60947, upload-time = "2025-11-07T00:44:46.086Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/3f/96db0619276a833842bf36343685fa04f987dd6e3037f314531a1e00492b/wrapt-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e", size = 59359, upload-time = "2025-11-07T00:44:47.164Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/49/5f5d1e867bf2064bf3933bc6cf36ade23505f3902390e175e392173d36a2/wrapt-2.0.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b", size = 82031, upload-time = "2025-11-07T00:44:49.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/89/0009a218d88db66ceb83921e5685e820e2c61b59bbbb1324ba65342668bc/wrapt-2.0.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec", size = 62952, upload-time = "2025-11-07T00:44:50.74Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/18/9b968e920dd05d6e44bcc918a046d02afea0fb31b2f1c80ee4020f377cbe/wrapt-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa", size = 63688, upload-time = "2025-11-07T00:44:52.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/7d/78bdcb75826725885d9ea26c49a03071b10c4c92da93edda612910f150e4/wrapt-2.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815", size = 152706, upload-time = "2025-11-07T00:44:54.613Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/77/cac1d46f47d32084a703df0d2d29d47e7eb2a7d19fa5cbca0e529ef57659/wrapt-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa", size = 158866, upload-time = "2025-11-07T00:44:55.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/11/b521406daa2421508903bf8d5e8b929216ec2af04839db31c0a2c525eee0/wrapt-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef", size = 146148, upload-time = "2025-11-07T00:44:53.388Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/c0/340b272bed297baa7c9ce0c98ef7017d9c035a17a6a71dce3184b8382da2/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747", size = 155737, upload-time = "2025-11-07T00:44:56.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/93/bfcb1fb2bdf186e9c2883a4d1ab45ab099c79cbf8f4e70ea453811fa3ea7/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f", size = 144451, upload-time = "2025-11-07T00:44:58.515Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/6b/dca504fb18d971139d232652656180e3bd57120e1193d9a5899c3c0b7cdd/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349", size = 150353, upload-time = "2025-11-07T00:44:59.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/f6/a1de4bd3653afdf91d250ca5c721ee51195df2b61a4603d4b373aa804d1d/wrapt-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c", size = 60609, upload-time = "2025-11-07T00:45:03.315Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/3a/07cd60a9d26fe73efead61c7830af975dfdba8537632d410462672e4432b/wrapt-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395", size = 64038, upload-time = "2025-11-07T00:45:00.948Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/99/8a06b8e17dddbf321325ae4eb12465804120f699cd1b8a355718300c62da/wrapt-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad", size = 60634, upload-time = "2025-11-07T00:45:02.087Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user