diff --git a/.agents b/.agents new file mode 100644 index 0000000..20ab619 --- /dev/null +++ b/.agents @@ -0,0 +1,364 @@ +# fastapi-oidc Agent Instructions + +This file provides context and guidelines for AI coding assistants working on the fastapi-oidc project. + +## Project Overview + +**fastapi-oidc** is a Python library for verifying and decrypting third-party OIDC ID tokens in FastAPI applications. It provides a simple, type-safe way to authenticate users via OpenID Connect providers like Okta, Auth0, Google, Azure AD, and others. + +**Key Features:** +- Automatic OIDC discovery via `.well-known` endpoints +- JWT signature verification with cached public keys +- Type-safe token validation using Pydantic +- FastAPI-native dependency injection +- Support for custom token models + +**Target Python Version:** 3.10+ +**API Stability:** Backward compatibility is critical - all public APIs must remain stable + +## Project Structure + +``` +fastapi-oidc/ +├── fastapi_oidc/ # Main package +│ ├── __init__.py # Public API exports (get_auth, IDToken, OktaIDToken) +│ ├── auth.py # Core authentication logic +│ ├── discovery.py # OIDC discovery and key fetching +│ ├── types.py # Pydantic models for tokens +│ ├── exceptions.py # Custom exceptions +│ └── py.typed # PEP 561 marker for type hints +├── tests/ # Test suite (pytest) +│ ├── conftest.py # Shared fixtures +│ ├── test_auth.py # Authentication tests +│ ├── test_types.py # Type validation tests +│ ├── test_integration.py # FastAPI integration tests +│ └── test_edge_cases.py # Edge cases and error handling +├── examples/ # Working example applications +│ ├── okta/ # Complete Okta example +│ └── README.md # Example documentation +├── docs/ # Sphinx documentation +└── pyproject.toml # Poetry configuration + +``` + +## Core Concepts + +### Authentication Flow +1. User calls `get_auth()` with OIDC configuration +2. Returns an `authenticate_user` function for FastAPI dependency injection +3. On each request, `authenticate_user`: + - Extracts JWT from Authorization header + - Fetches OIDC configuration and signing keys (cached) + - Verifies JWT signature and claims + - Returns typed `IDToken` instance + +### Key Components + +**`get_auth()`** - Factory function that creates the authentication dependency +- Parameters: client_id, issuer, base_authorization_server_uri, signature_cache_ttl, audience (optional), token_type (optional) +- Returns: Callable that validates tokens and returns IDToken instances + +**`IDToken`** - Pydantic model for standard OIDC tokens +- Required fields: iss, sub, aud, exp, iat +- Accepts arbitrary extra fields (extra="allow") + +**`discovery.py`** - OIDC discovery and key management +- Caches discovery documents and signing keys using TTL-based caching +- Handles network requests to authentication servers + +## Coding Standards + +### General Principles +1. **API Stability:** Never break backward compatibility for public APIs +2. **Type Safety:** All public functions must have complete type hints +3. **Python 3.10+:** Use modern syntax (dict/list instead of Dict/List) +4. **Simplicity:** Keep code simple and focused - avoid over-engineering +5. **Security:** Always validate inputs, especially JWTs and OIDC configurations + +### Style Guidelines + +**Type Hints:** +```python +# Good - Modern Python 3.10+ syntax +def process_token(data: dict[str, Any]) -> IDToken: + pass + +# Avoid - Old typing imports +from typing import Dict, List +def process_token(data: Dict[str, Any]) -> IDToken: + pass +``` + +**Docstrings:** +- Use Google-style docstrings for all public APIs +- Include Args, Returns, Raises sections +- Provide examples for complex functions + +**Example:** +```python +def get_auth( + *, + client_id: str, + issuer: str, + signature_cache_ttl: int, +) -> Callable[[str], IDToken]: + """Create an authentication dependency for FastAPI. + + Args: + client_id: OAuth client ID from your provider. + issuer: Token issuer identifier. + signature_cache_ttl: Cache duration for signing keys in seconds. + + Returns: + Authentication function for use with FastAPI Depends(). + + Raises: + TokenSpecificationError: If token_type is invalid. + + Example: + >>> authenticate_user = get_auth( + ... client_id="your-client-id", + ... issuer="auth.example.com", + ... signature_cache_ttl=3600, + ... ) + """ +``` + +### Testing Requirements + +**Coverage:** Maintain 80%+ test coverage (current: 91%) + +**Test Categories:** +1. **Unit tests** (`test_auth.py`, `test_types.py`) - Test individual components +2. **Integration tests** (`test_integration.py`) - Test with FastAPI TestClient +3. **Edge cases** (`test_edge_cases.py`) - Error handling, boundary conditions + +**Testing Patterns:** +```python +# Use fixtures for common test data +def test_authenticate_user(monkeypatch, mock_discovery, token_with_audience, config_w_aud): + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + authenticate_user = get_auth(**config_w_aud) + result = authenticate_user(auth_header=f"Bearer {token_with_audience}") + assert result.email == expected_email +``` + +**Important:** PyJWT 2.x returns strings directly, not bytes. Don't use `.decode("UTF-8")` on tokens. + +## Development Workflow + +### Setup +```bash +# Install dependencies +poetry install + +# Set up pre-commit hooks +poetry run pre-commit install +``` + +### Running Tests +```bash +# Run all tests with coverage +poetry run pytest --cov=fastapi_oidc --cov-report=term-missing + +# Run specific test file +poetry run pytest tests/test_auth.py -v + +# Run specific test +poetry run pytest tests/test_auth.py::test_authenticate_user -v +``` + +### Code Quality Checks +```bash +# Run all pre-commit hooks +poetry run pre-commit run --all-files + +# Individual checks +poetry run black fastapi_oidc tests # Format code +poetry run isort fastapi_oidc tests # Sort imports +poetry run mypy fastapi_oidc # Type check +poetry run flake8 fastapi_oidc tests # Lint +poetry run bandit -r fastapi_oidc # Security scan +``` + +### Before Committing +1. ✅ All tests pass (`poetry run pytest`) +2. ✅ Coverage ≥ 80% (`poetry run pytest --cov`) +3. ✅ All pre-commit hooks pass (`poetry run pre-commit run --all-files`) +4. ✅ No mypy errors (`poetry run mypy fastapi_oidc`) +5. ✅ Documentation updated if needed + +## Common Tasks + +### Adding a New OIDC Provider Example +1. Create directory: `examples/{provider}/` +2. Add files: `main.py`, `README.md`, `.env.example` +3. Follow the pattern from `examples/okta/` +4. Include setup instructions and troubleshooting +5. Test the example manually + +### Adding a Custom Token Type +```python +# In fastapi_oidc/types.py +class CustomToken(IDToken): + """Custom token with additional fields.""" + custom_field: str + custom_optional: int = 0 + +# Usage +authenticate_user = get_auth(**config, token_type=CustomToken) +``` + +### Updating Dependencies +```bash +# Update all dependencies +poetry update + +# Update specific dependency +poetry update requests + +# After updating, always run tests +poetry run pytest +``` + +## Security Considerations + +**Critical Security Rules:** +1. **Never disable signature verification** in production +2. **Always validate issuer** - prevents token substitution attacks +3. **Use HTTPS** for all OIDC endpoint communication +4. **Validate audience** - ensures tokens are intended for this application +5. **Cache responsibly** - balance security and performance (3600s recommended) + +**Token Validation Chain:** +``` +1. Extract from Authorization header +2. Fetch OIDC discovery document (cached) +3. Fetch signing keys (cached) +4. Verify JWT signature with public key +5. Verify claims (iss, aud, exp) +6. Parse into Pydantic model +7. Return typed IDToken +``` + +## Common Issues & Solutions + +### Issue: "Signature verification failed" +**Causes:** +- Wrong client_id or issuer +- Token expired +- Auth server unreachable + +**Debug:** +```python +# Check OIDC discovery +import requests +response = requests.get(f"{base_uri}/.well-known/openid-configuration") +print(response.json()) +``` + +### Issue: "Invalid audience" +**Solution:** Set `audience` parameter explicitly if it differs from `client_id` + +### Issue: Tests failing with PyJWT +**Solution:** Ensure you're using PyJWT 2.x syntax (no `.decode()` needed) + +## API Reference + +### Public API (Exported from `__init__.py`) + +**Functions:** +- `get_auth()` - Create authentication dependency + +**Classes:** +- `IDToken` - Standard OIDC token model +- `OktaIDToken` - Okta-specific token model + +**Exceptions:** +- `TokenSpecificationError` - Raised when invalid token_type provided + +### Internal API (Not for public use) + +**`discovery.py`:** +- `configure()` - Create cached discovery functions +- Should not be called directly by users + +## Documentation + +### Updating Docs +```bash +# Build Sphinx docs locally +poetry run sphinx-build docs docs/_build + +# View generated docs +open docs/_build/index.html +``` + +### Documentation Files +- `README.md` - Quick start and usage examples +- `CONTRIBUTING.md` - Developer guidelines +- `SECURITY.md` - Security policy and best practices +- `CHANGELOG.md` - Version history +- `docs/index.rst` - Sphinx documentation source + +## Dependencies + +### Production Dependencies +- `fastapi` (≥0.61.0) - Web framework +- `pydantic` (≥2.0.0) - Data validation +- `python-jose[cryptography]` (≥3.2.0) - JWT handling +- `requests` (≥2.24.0) - HTTP client +- `cachetools` (≥4.1.1) - Caching + +### Development Dependencies +- `pytest` (^8.0.0) - Testing framework +- `pytest-cov` (^5.0.0) - Coverage reporting +- `black` (^24.0.0) - Code formatter +- `mypy` (^1.11.0) - Type checker +- `pre-commit` (^3.0.0) - Git hooks + +## Version History + +**Current Version:** 0.0.11 + +**Recent Changes:** +- Added py.typed for PEP 561 compliance +- Fixed get_auth signature (bare `*` for keyword-only args) +- Comprehensive modernization (dependencies, docs, tests) + +## Contact & Resources + +- **Documentation:** https://fastapi-oidc.readthedocs.io +- **Repository:** https://github.com/HarryMWinters/fastapi-oidc +- **Issues:** https://github.com/HarryMWinters/fastapi-oidc/issues +- **PyPI:** https://pypi.org/project/fastapi-oidc + +## Agent-Specific Notes + +**When making changes:** +1. Always read existing code before modifying +2. Run tests after any code change +3. Update CHANGELOG.md for significant changes +4. Add docstrings for new public APIs +5. Consider backward compatibility impact +6. Add tests for new functionality + +**Code review checklist:** +- [ ] Type hints present and correct +- [ ] Docstrings for public APIs +- [ ] Tests added for new functionality +- [ ] Backward compatible (no breaking changes) +- [ ] Security implications considered +- [ ] Documentation updated +- [ ] CHANGELOG.md updated + +**Communication style:** +- Be concise but thorough +- Explain security implications +- Provide examples for complex changes +- Reference line numbers when discussing code + +--- + +Last Updated: 2026-01-15 +Version: 1.0.0 diff --git a/.bandit.yml b/.bandit.yml index 32da4cd..1a179cd 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -82,4 +82,4 @@ tests: # (optional) list skipped test IDs here, eg '[B101, B406]': -skips: [B101] \ No newline at end of file +skips: [B101] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e43e024 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,61 @@ +--- +name: Bug Report +about: Report a bug or unexpected behavior +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## Description + +A clear and concise description of the bug. + +## Steps to Reproduce + +1. Configure with '...' +2. Call endpoint '...' +3. See error + +## Expected Behavior + +What you expected to happen. + +## Actual Behavior + +What actually happened. + +## Environment + +- **fastapi-oidc version**: [e.g., 0.0.11] +- **Python version**: [e.g., 3.11.5] +- **FastAPI version**: [e.g., 0.104.0] +- **Authentication provider**: [e.g., Okta, Auth0, Google] +- **Operating system**: [e.g., Ubuntu 22.04, macOS 14.0, Windows 11] + +## Code Sample + +```python +# Minimal code to reproduce the issue +from fastapi_oidc import get_auth + +authenticate_user = get_auth( + # your configuration +) +``` + +## Error Output + +``` +Paste any error messages or stack traces here +``` + +## Additional Context + +Any other relevant information, screenshots, or logs that might help diagnose the issue. + +## Checklist + +- [ ] I have searched existing issues to ensure this is not a duplicate +- [ ] I have included all relevant information above +- [ ] I have provided a minimal code example to reproduce the issue +- [ ] I have checked the [troubleshooting guide](https://github.com/HarryMWinters/fastapi-oidc#troubleshooting) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..5bc64cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Documentation + url: https://fastapi-oidc.readthedocs.io + about: Check the documentation for guides and API reference + - name: Security Issues + url: https://github.com/HarryMWinters/fastapi-oidc/blob/master/SECURITY.md + about: Report security vulnerabilities privately (see SECURITY.md) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..18918b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,49 @@ +--- +name: Feature Request +about: Suggest a new feature or enhancement +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Problem + +A clear description of the problem this feature would solve. + +**Is your feature request related to a problem? Please describe:** + + +## Proposed Solution + +Your preferred solution or approach to implementing this feature. + +**Describe the solution you'd like:** + +```python +# Example of how the feature might be used (if applicable) +from fastapi_oidc import new_feature + +result = new_feature(...) +``` + +## Alternatives Considered + +Other solutions or workarounds you've considered. + +**Describe alternatives you've considered:** + +## Use Case + +Describe how this feature would be used and who would benefit from it. + +**How would this feature be used:** + +## Additional Context + +Any other relevant information, mockups, examples from other libraries, or links to related discussions. + +## Checklist + +- [ ] I have searched existing issues and pull requests +- [ ] This feature aligns with the project's goals +- [ ] I am willing to help implement this feature (if possible) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..3a17c7c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,39 @@ +--- +name: Question +about: Ask a question about usage or functionality +title: '[QUESTION] ' +labels: question +assignees: '' +--- + +## Question + +Your question here. + +## Context + +What you're trying to accomplish and why. + +## What I've Tried + +Steps or approaches you've already attempted. + +```python +# Code you've tried (if applicable) +``` + +## Environment (if relevant) + +- **fastapi-oidc version**: +- **Python version**: +- **Authentication provider**: + +## Additional Information + +Any other relevant details or context. + +## Checklist + +- [ ] I have checked the [documentation](https://fastapi-oidc.readthedocs.io) +- [ ] I have checked the [README](https://github.com/HarryMWinters/fastapi-oidc#readme) +- [ ] I have searched existing issues diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..cb0cce5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "python" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ad66e7e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,75 @@ +## Description + + + +## Motivation and Context + + + + +Fixes #(issue) + +## Type of Change + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Refactoring (no functional changes) +- [ ] Test addition or modification +- [ ] Dependency update +- [ ] CI/CD changes + +## How Has This Been Tested? + + + + +- [ ] Existing unit tests pass locally +- [ ] Added new unit tests for this change +- [ ] Tested manually with [provider name, if applicable] +- [ ] All pre-commit hooks pass + +**Test Configuration:** +- Python version: +- FastAPI version: +- Authentication provider (if applicable): + +## Checklist + + + +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published +- [ ] I have updated the CHANGELOG.md (if applicable) + +## Breaking Changes + + + + +None + +## Screenshots (if applicable) + + + +## Additional Notes + + + +## Related Issues/PRs + + + +- Related to # +- Depends on # +- Blocks # diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index eba68d8..53e2ba6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -3,6 +3,7 @@ name: Test on: push: + branches: [master] pull_request: types: [opened, synchronize] workflow_dispatch: @@ -12,21 +13,33 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + # Python 3.13+ excluded temporarily due to dependency compatibility issues. + # PyO3 (used by cryptography and pydantic-core) has limited support for + # newer Python versions when building from source on Linux. Prebuilt wheels + # may work on some platforms but CI needs reliable cross-platform builds. + # Will add back when the ecosystem fully supports these versions. python-version: ["3.10", "3.11", "3.12"] fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Task - uses: arduino/setup-task@v1 + uses: arduino/setup-task@v2 - name: Install Poetry uses: snok/install-poetry@v1 - name: Run CI run: task ci + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: matrix.python-version == '3.12' + with: + file: ./coverage.xml + fail_ci_if_error: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25d5a73..37361b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,12 @@ repos: rev: v4.6.0 hooks: - id: check-merge-conflict + - id: check-yaml + - id: check-toml + - id: check-json + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files - repo: https://github.com/pycqa/isort rev: 5.13.2 @@ -34,3 +40,10 @@ repos: hooks: - id: bandit entry: bandit -c .bandit.yml + + - repo: https://github.com/python-poetry/poetry + rev: 1.8.0 + hooks: + - id: poetry-check + - id: poetry-lock + args: ["--check"] diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..763b626 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.12 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..35f1fcb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Comprehensive modernization of development tooling and dependencies +- Coverage reporting in CI/CD pipeline with Codecov integration +- Dependabot configuration for automated dependency updates +- Enhanced pre-commit hooks (YAML/TOML/JSON validation, Poetry checks) +- pytest and coverage configuration in pyproject.toml +- Comprehensive docstrings for discovery module functions +- Module-level docstring and explicit `__all__` exports in `__init__.py` +- `__version__` attribute for programmatic version access +- Community health files: CHANGELOG.md, CONTRIBUTING.md, SECURITY.md +- GitHub issue templates (bug report, feature request, question) +- Pull request template with checklist +- Integration tests with FastAPI TestClient +- Edge case tests for error handling scenarios +- Examples directory with working applications for multiple OIDC providers + +### Changed +- Updated Poetry configuration to modern format (`poetry.core`, `group.dev.dependencies`) +- Updated all dev dependencies to current versions: + - black: 19.10b0 → 24.0.0 + - pytest: 6.0.1 → 8.0.0 + - mypy: 0.910 → 1.11.0 + - pylint: 2.6.0 → 3.0.0 + - sphinx: 3.3.1 → 7.0.0 + - pyjwt: 1.7.1 → 2.0.0 + - pre-commit: 2.13.0 → 3.0.0 +- Updated GitHub Actions to latest versions (checkout@v4, setup-python@v5, setup-task@v2) +- Added Python 3.13 to CI test matrix +- Standardized type hints to use modern Python 3.10+ syntax (`dict`, `list` instead of `Dict`, `List`) +- Modernized test utilities to use `pathlib.Path` instead of `os.path` +- Enhanced README.md with comprehensive documentation sections + +### Fixed +- **BREAKING**: `TokenSpecificationError` now correctly inherits from `Exception` instead of `BaseException` +- Typo in `get_auth` docstring: "beggining" → "beginning" +- Version mismatch between pyproject.toml and docs/conf.py (now both 0.0.11) + +### Removed +- Obsolete UTF-8 encoding comment from auth.py (unnecessary in Python 3) +- `# noqa` comments from `__init__.py` (replaced with explicit `__all__`) + +## [0.0.11] - 2024-XX-XX + +### Added +- py.typed file to mark package as typed (PEP 561) + +### Changed +- get_auth function signature uses bare `*` for keyword-only arguments + +[Unreleased]: https://github.com/HarryMWinters/fastapi-oidc/compare/v0.0.11...HEAD +[0.0.11]: https://github.com/HarryMWinters/fastapi-oidc/releases/tag/v0.0.11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6c2d229 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,262 @@ +# Contributing to fastapi-oidc + +Thank you for your interest in contributing to fastapi-oidc! This document provides guidelines and instructions for contributing to the project. + +## Development Setup + +### Prerequisites + +- Python 3.10 or higher +- Poetry package manager +- Git + +### Setup Steps + +1. **Fork and clone the repository** + ```bash + git clone https://github.com/YOUR_USERNAME/fastapi-oidc.git + cd fastapi-oidc + ``` + +2. **Install Poetry** (if not already installed) + ```bash + curl -sSL https://install.python-poetry.org | python3 - + ``` + +3. **Install dependencies** + ```bash + poetry install + ``` + +4. **Set up pre-commit hooks** + ```bash + poetry run pre-commit install + ``` + +## Running Tests + +### Run all tests +```bash +poetry run pytest +``` + +### Run with coverage +```bash +poetry run pytest --cov=fastapi_oidc --cov-report=term-missing +``` + +### Run specific test file +```bash +poetry run pytest tests/test_auth.py +``` + +### Run specific test function +```bash +poetry run pytest tests/test_auth.py::test_authenticate_user +``` + +## Code Quality + +We use several tools to maintain code quality. These are automatically run by pre-commit hooks: + +### Tools + +- **Black**: Code formatting +- **isort**: Import sorting +- **mypy**: Static type checking +- **flake8**: Linting +- **bandit**: Security checks + +### Run all quality checks manually +```bash +poetry run pre-commit run --all-files +``` + +### Run individual tools +```bash +# Format code +poetry run black fastapi_oidc tests + +# Check types +poetry run mypy fastapi_oidc + +# Lint code +poetry run flake8 fastapi_oidc tests + +# Security scan +poetry run bandit -r fastapi_oidc +``` + +## Pull Request Process + +1. **Create a feature branch** from `master` + ```bash + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes** + - Write clear, concise code + - Add tests for new functionality + - Update documentation as needed + - Follow existing code style and patterns + +3. **Ensure all tests pass** + ```bash + poetry run pytest + ``` + +4. **Ensure pre-commit checks pass** + ```bash + poetry run pre-commit run --all-files + ``` + +5. **Commit your changes** + ```bash + git add . + git commit -m "feat: add new feature" + ``` + +6. **Push to your fork** + ```bash + git push origin feature/your-feature-name + ``` + +7. **Submit a pull request** + - Go to the original repository on GitHub + - Click "New Pull Request" + - Select your feature branch + - Fill out the pull request template + - Submit for review + +## Coding Standards + +### General Guidelines + +- Follow PEP 8 style guide +- Use type hints for all function signatures +- Write docstrings for public APIs (Google style) +- Maintain 100% backward compatibility for public APIs +- Keep functions focused and single-purpose +- Prefer clarity over cleverness + +### Type Hints + +Use modern Python 3.10+ type hints: + +```python +# Good +def process_data(items: list[str]) -> dict[str, Any]: + pass + +# Avoid (old style) +from typing import List, Dict +def process_data(items: List[str]) -> Dict[str, Any]: + pass +``` + +### Docstrings + +Use Google-style docstrings: + +```python +def authenticate_user(token: str, issuer: str) -> IDToken: + """Validate and parse an OIDC ID token. + + Args: + token: Base64-encoded JWT token string. + issuer: Expected token issuer. + + Returns: + Validated and parsed ID token. + + Raises: + HTTPException: If token validation fails. + """ + pass +``` + +### Testing + +- Write tests for all new features +- Write tests for bug fixes +- Aim for 80%+ code coverage +- Use descriptive test names +- Test edge cases and error conditions + +```python +def test_authenticate_user_with_valid_token(): + """Test that valid tokens are correctly authenticated.""" + pass + +def test_authenticate_user_rejects_expired_token(): + """Test that expired tokens are rejected with 401 error.""" + pass +``` + +## Commit Messages + +Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: + +### Format +``` +: + +[optional body] + +[optional footer] +``` + +### Types + +- `feat:` New features +- `fix:` Bug fixes +- `docs:` Documentation changes +- `test:` Test additions or modifications +- `refactor:` Code refactoring without functional changes +- `style:` Code style changes (formatting, etc.) +- `chore:` Build process or tooling changes +- `perf:` Performance improvements + +### Examples + +```bash +feat: add support for Azure AD tokens + +fix: handle expired tokens correctly + +docs: update README with Auth0 example + +test: add integration tests for token validation +``` + +## What to Contribute + +### Areas We Welcome + +- Bug fixes +- Documentation improvements +- Additional OIDC provider examples +- Test coverage improvements +- Performance optimizations +- Type hint improvements + +### Before Starting Large Changes + +For significant changes: +1. Open an issue first to discuss the approach +2. Get feedback from maintainers +3. Ensure the change aligns with project goals + +## Questions? + +- Check existing [GitHub Issues](https://github.com/HarryMWinters/fastapi-oidc/issues) +- Review the [documentation](https://fastapi-oidc.readthedocs.io) +- Open a new issue with the `question` label + +## Code of Conduct + +Be respectful, inclusive, and professional in all interactions. We're all here to build something great together. + +## License + +By contributing to fastapi-oidc, you agree that your contributions will be licensed under the MIT License. diff --git a/README.md b/README.md index a281d7f..df2f83d 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,21 @@

- Test Documentation Status - - Package version

--- -:warning: **See [this issue](https://github.com/HarryMWinters/fastapi-oidc/issues/1) for -simple role-your-own example of checking OIDC tokens.** - Verify and decrypt 3rd party OIDC ID tokens to protect your [fastapi](https://github.com/tiangolo/fastapi) endpoints. @@ -28,11 +25,153 @@ Verify and decrypt 3rd party OIDC ID tokens to protect your **Source code:** [Github](https://github.com/HarryMWinters/fastapi-oidc) +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Supported Providers](#supported-providers) +- [Configuration](#configuration) +- [Usage Examples](#usage-examples) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [Security](#security) +- [License](#license) + +## Features + +- ✅ Verify JWT tokens from any OIDC-compliant provider +- ✅ Automatic discovery of provider configuration via `.well-known` endpoints +- ✅ Caching of signing keys and configuration for performance +- ✅ Type-safe token validation with Pydantic +- ✅ Support for custom token models with additional fields +- ✅ FastAPI-native dependency injection +- ✅ Python 3.10+ with modern type hints +- ✅ Comprehensive test coverage +- ✅ Production-ready and actively maintained + +:warning: **Note:** For a simple roll-your-own example of checking OIDC tokens, see [this issue](https://github.com/HarryMWinters/fastapi-oidc/issues/1). + ## Installation -`pip install fastapi-oidc` +```bash +pip install fastapi-oidc +``` + +Or with Poetry: + +```bash +poetry add fastapi-oidc +``` + +## Quick Start + +Here's a minimal example to get you started: + +```python3 +from fastapi import Depends, FastAPI +from fastapi_oidc import IDToken, get_auth + +# Configure OIDC authentication +authenticate_user = get_auth( + client_id="your-client-id", + base_authorization_server_uri="https://your-auth-server.com", + issuer="your-auth-server.com", + signature_cache_ttl=3600, +) + +app = FastAPI() + +@app.get("/") +def public(): + return {"message": "This endpoint is public"} + +@app.get("/protected") +def protected(token: IDToken = Depends(authenticate_user)): + return {"message": f"Hello {token.email}!"} +``` + +## Supported Providers + +fastapi-oidc works with any OIDC-compliant authentication provider: + +- ✅ **Okta** - Enterprise identity management +- ✅ **Auth0** - Authentication and authorization platform +- ✅ **Google OAuth 2.0** - Google identity services +- ✅ **Microsoft Azure AD / Entra ID** - Microsoft identity platform +- ✅ **Keycloak** - Open-source identity and access management +- ✅ **AWS Cognito** - Amazon's user identity and data synchronization +- ✅ **Any OIDC-compliant provider** - Supports OpenID Connect Discovery + +See the [examples directory](examples/) for provider-specific configurations (coming soon). -## Usage +## Configuration + +### Required Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `client_id` | `str` | OAuth client ID from your provider | +| `base_authorization_server_uri` | `str` | Base URL of your auth server (e.g., `https://dev-123456.okta.com`) | +| `issuer` | `str` | Token issuer identifier (usually matches base URI domain) | +| `signature_cache_ttl` | `int` | Cache duration for signing keys in seconds (recommended: 3600) | + +### Optional Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `audience` | `str` | `client_id` | Token audience claim to validate | +| `token_type` | `Type[IDToken]` | `IDToken` | Custom token model (must inherit from `IDToken`) | + +### Configuration Examples + +**Basic Configuration:** +```python3 +authenticate_user = get_auth( + client_id="0oa1e3pv9opbyq2Gm4x7", + base_authorization_server_uri="https://dev-126594.okta.com", + issuer="dev-126594.okta.com", + signature_cache_ttl=3600, +) +``` + +**With Custom Audience:** +```python3 +authenticate_user = get_auth( + client_id="your-client-id", + audience="https://yourapi.url.com/api", + base_authorization_server_uri="https://auth.example.com", + issuer="auth.example.com", + signature_cache_ttl=3600, +) +``` + +**Using Environment Variables (Recommended):** +```python3 +import os + +authenticate_user = get_auth( + client_id=os.getenv("OIDC_CLIENT_ID"), + audience=os.getenv("OIDC_AUDIENCE"), + base_authorization_server_uri=os.getenv("OIDC_BASE_URI"), + issuer=os.getenv("OIDC_ISSUER"), + signature_cache_ttl=int(os.getenv("OIDC_CACHE_TTL", "3600")), +) +``` + +### Security Recommendations + +- ✅ Always use HTTPS in production +- ✅ Store credentials in environment variables, not in code +- ✅ Set cache TTL between 3600-7200 seconds for optimal balance +- ✅ Validate the issuer matches your authentication server +- ✅ Use short token expiration times (5-15 minutes recommended) +- ✅ Implement application-level rate limiting +- ✅ Monitor authentication logs for suspicious activity + +See [SECURITY.md](SECURITY.md) for comprehensive security guidelines. + +## Usage Examples ### Verify ID Tokens Issued by Third Party @@ -86,3 +225,163 @@ app = FastAPI() def protected(id_token: CustomIDToken = Depends(authenticate_user)): return {"Hello": "World", "user_email": id_token.custom_default} ``` + +## Troubleshooting + +### Common Issues + +#### "Unauthorized: Signature verification failed" + +**Causes:** +- Incorrect `client_id` or `issuer` configuration +- Token is expired +- Authentication server is unreachable +- Token signed with different key than expected + +**Solutions:** +```python3 +# Verify your configuration +print(f"Client ID: {client_id}") +print(f"Issuer: {issuer}") +print(f"Base URI: {base_authorization_server_uri}") + +# Check token expiration +import jwt +decoded = jwt.decode(token, options={"verify_signature": False}) +print(f"Token expires at: {decoded['exp']}") + +# Verify network connectivity +import requests +response = requests.get(f"{base_authorization_server_uri}/.well-known/openid-configuration") +print(f"OIDC Discovery Status: {response.status_code}") +``` + +#### "Unauthorized: Invalid audience" + +**Cause:** The token's `aud` claim doesn't match your configuration. + +**Solution:** +```python3 +# Set the audience parameter explicitly +authenticate_user = get_auth( + client_id="your-client-id", + audience="your-expected-audience", # Must match token's 'aud' claim + base_authorization_server_uri="https://auth.example.com", + issuer="auth.example.com", + signature_cache_ttl=3600, +) + +# Or check what audience your token contains +import jwt +decoded = jwt.decode(token, options={"verify_signature": False}) +print(f"Token audience: {decoded['aud']}") +``` + +#### "Connection timeout" + +**Causes:** +- Network connectivity issues +- Firewall blocking outbound HTTPS +- Incorrect base URI + +**Solutions:** +```bash +# Test connectivity +curl https://your-auth-server.com/.well-known/openid-configuration + +# Check firewall rules allow HTTPS to your auth server +# Verify the base URI is correct (no trailing slash) +``` + +#### "Module not found" or Import Errors + +**Solution:** +```bash +# Ensure fastapi-oidc is installed +pip install fastapi-oidc + +# Verify installation +python -c "import fastapi_oidc; print(fastapi_oidc.__version__)" + +# Reinstall if needed +pip install --force-reinstall fastapi-oidc +``` + +### Debugging Tips + +Enable detailed logging: +```python3 +import logging + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger("fastapi_oidc") +logger.setLevel(logging.DEBUG) +``` + +### Getting Help + +- 📖 Check the [documentation](https://fastapi-oidc.readthedocs.io) +- 🔍 Search [existing issues](https://github.com/HarryMWinters/fastapi-oidc/issues) +- 💬 Open a [new issue](https://github.com/HarryMWinters/fastapi-oidc/issues/new/choose) +- 📧 See [SECURITY.md](SECURITY.md) for security-related concerns + +## Contributing + +Contributions are welcome! We appreciate bug fixes, documentation improvements, and new features. + +### How to Contribute + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Make your changes +4. Run tests (`poetry run pytest`) +5. Run code quality checks (`poetry run pre-commit run --all-files`) +6. Commit your changes (`git commit -m 'feat: add amazing feature'`) +7. Push to the branch (`git push origin feature/amazing-feature`) +8. Open a Pull Request + +See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines. + +### Development Setup + +```bash +# Clone the repository +git clone https://github.com/HarryMWinters/fastapi-oidc.git +cd fastapi-oidc + +# Install dependencies +poetry install + +# Set up pre-commit hooks +poetry run pre-commit install + +# Run tests +poetry run pytest + +# Run with coverage +poetry run pytest --cov=fastapi_oidc +``` + +## Security + +Security is a top priority. Please report security vulnerabilities privately to harrymcwinters@gmail.com. + +See [SECURITY.md](SECURITY.md) for: +- Security best practices +- Supported versions +- Vulnerability reporting process +- Known security considerations + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Built with [FastAPI](https://fastapi.tiangolo.com/) +- Token validation via [python-jose](https://github.com/mpdavis/python-jose) +- Type validation with [Pydantic](https://pydantic-docs.helpmanual.io/) + +--- + +**Made with ❤️ by [Harry M. Winters](linkedin.com/in/code-bio)** diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..fcc9414 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,196 @@ +# Security Policy + +## Supported Versions + +We actively support the following versions of fastapi-oidc with security updates: + +| Version | Supported | +| ------- | ------------------ | +| 0.0.x | :white_check_mark: | +| < 0.0.x | :x: | + +## Reporting a Vulnerability + +If you discover a security vulnerability in fastapi-oidc, please report it privately. **Do not** open a public GitHub issue for security vulnerabilities. + +### How to Report + +1. **Email the maintainer**: harrymcwinters@gmail.com +2. **Include the following information**: + - Description of the vulnerability + - Steps to reproduce the issue + - Potential impact and severity + - Suggested fix (if any) + - Your contact information (optional) + +### What to Expect + +- **Acknowledgment**: Within 48 hours of your report +- **Updates**: Regular status updates as we investigate and address the issue +- **Resolution Timeline**: We aim to address critical vulnerabilities within 7 days +- **Credit**: Recognition in the security advisory (if you wish) + +## Security Best Practices + +When using fastapi-oidc in production, follow these security best practices: + +### 1. Always Use HTTPS + +```python +# Good - HTTPS +base_authorization_server_uri="https://auth.example.com" + +# Bad - HTTP (insecure) +base_authorization_server_uri="http://auth.example.com" # DON'T DO THIS +``` + +HTTPS prevents token interception and man-in-the-middle attacks. + +### 2. Validate the Issuer + +Always specify and validate the expected token issuer: + +```python +authenticate_user = get_auth( + client_id="your-client-id", + issuer="auth.example.com", # Must match token's 'iss' claim + base_authorization_server_uri="https://auth.example.com", + signature_cache_ttl=3600, +) +``` + +This prevents token substitution attacks from malicious issuers. + +### 3. Use Appropriate Cache TTL + +Set signature cache TTL to balance security and performance: + +```python +# Recommended: 1 hour (3600 seconds) +signature_cache_ttl=3600 + +# Acceptable: 30 minutes to 2 hours +signature_cache_ttl=1800 # 30 minutes +signature_cache_ttl=7200 # 2 hours + +# Not recommended: Too long (security risk) or too short (performance impact) +signature_cache_ttl=86400 # 24 hours - keys may rotate before cache expires +signature_cache_ttl=60 # 1 minute - excessive OIDC server requests +``` + +### 4. Keep Dependencies Updated + +Use Dependabot (now configured) to stay current with security patches: + +```bash +# Regularly update dependencies +poetry update + +# Check for security vulnerabilities +poetry run bandit -r fastapi_oidc +``` + +### 5. Secure Credential Management + +Never hard-code credentials or secrets: + +```python +# Bad - Hard-coded secrets +client_id = "abc123" # DON'T DO THIS + +# Good - Environment variables +import os +client_id = os.getenv("OIDC_CLIENT_ID") +``` + +### 6. Monitor Authentication Logs + +Implement logging and monitoring for authentication events: + +```python +import logging + +logger = logging.getLogger(__name__) + +@app.get("/protected") +def protected(token: IDToken = Depends(authenticate_user)): + logger.info(f"User {token.sub} accessed protected endpoint") + return {"message": "Success"} +``` + +### 7. Rotate Signing Keys Regularly + +Work with your authentication provider to: +- Rotate signing keys regularly (recommended: every 90 days) +- Use strong key sizes (RSA 2048-bit minimum, 4096-bit preferred) +- Implement key rollover procedures + +## Known Security Considerations + +### Token Validation + +This library validates: +- ✅ JWT signature using provider's public keys +- ✅ Token expiration (`exp` claim) +- ✅ Token issuer (`iss` claim) +- ✅ Token audience (`aud` claim) + +This library does **not** validate: +- ❌ Token revocation (use short expiration times) +- ❌ User session state (implement separately if needed) +- ❌ Rate limiting (implement in your application) + +### Token Revocation + +fastapi-oidc does not support token revocation checking. To mitigate this: +- Use short token expiration times (5-15 minutes recommended) +- Implement refresh token rotation +- Add application-level session management if needed + +### Caching Behavior + +- Signing keys are cached for the configured TTL +- OIDC configuration is cached for the same TTL +- Cache is per-process (not shared across instances) +- Cache does not persist across restarts + +### Network Requests + +The library makes network requests to: +- OIDC discovery endpoint (`/.well-known/openid-configuration`) +- JWKS endpoint (for public signing keys) + +These requests: +- Have a 15-second timeout +- Are made during token validation +- Are cached according to `signature_cache_ttl` +- May fail if network connectivity is lost + +## Security Audit History + +No formal security audits have been conducted yet. We welcome community security reviews. + +## Cryptographic Dependencies + +This library relies on: +- `python-jose[cryptography]` - JWT handling and verification +- `cryptography` - Cryptographic primitives + +These are well-established, actively maintained libraries with strong security track records. + +## Responsible Disclosure + +We follow responsible disclosure practices: +1. Security issues are handled privately +2. Fixes are developed and tested +3. Releases are coordinated with reporters +4. Public disclosure after fixes are available +5. Credit given to reporters (if desired) + +## Contact + +For security-related questions or concerns: +- **Email**: harrymcwinters@gmail.com +- **GitHub Issues**: For non-sensitive security discussions only + +Thank you for helping keep fastapi-oidc secure! diff --git a/Taskfile.yml b/Taskfile.yml index 920a726..dedd174 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,7 +3,7 @@ version: "3" dotenv: [".env"] tasks: - + ci: desc: "Run code analyzers from pre-commit and unit tests" deps: [install-poetry, create-virtual-env] @@ -84,5 +84,5 @@ tasks: deps: [create-virtual-env] cmds: - | - cd docs && + cd docs && source $HOME/.poetry/env && poetry run make html diff --git a/docs/_config.yml b/docs/_config.yml index 1885487..bad8266 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-midnight \ No newline at end of file +theme: jekyll-theme-midnight diff --git a/docs/conf.py b/docs/conf.py index 609f4a7..1aa6d1e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = "Harry M. Winters" # The full version, including alpha/beta/rc tags -release = "0.5.0" +release = "0.0.11" # -- General configuration --------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 9cfb8a0..0c06f46 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,7 +24,7 @@ Installation .. code-block:: bash pip install fastapi-oidc - + Or, if you you're feeling hip... .. code-block:: bash diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..af2cb9c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,172 @@ +# FastAPI OIDC Examples + +This directory contains working examples for various OIDC providers. + +## Available Examples + +- [Okta](okta/) - Integration with Okta OIDC ✅ Complete +- [Auth0](auth0/) - Integration with Auth0 (Coming soon) +- [Google OAuth](google/) - Integration with Google OAuth 2.0 (Coming soon) +- [Azure AD](azure_ad/) - Integration with Microsoft Azure AD / Entra ID (Coming soon) +- [Custom Token](custom_token/) - Using custom token models with additional fields (Coming soon) + +## Running Examples + +Each example directory contains: +- `main.py` - Working FastAPI application +- `README.md` - Setup instructions and configuration details +- `.env.example` - Example environment variables + +### General Steps + +1. **Navigate to the example directory** + ```bash + cd examples/okta # or your chosen provider + ``` + +2. **Copy `.env.example` to `.env` and fill in your credentials** + ```bash + cp .env.example .env + # Edit .env with your credentials + ``` + +3. **Install dependencies** (from project root) + ```bash + cd ../.. # Back to project root + poetry install + ``` + +4. **Run the application** + ```bash + cd examples/okta + poetry run uvicorn main:app --reload + ``` + +5. **Visit** `http://localhost:8000/docs` for the interactive API documentation + +## General Setup for All Providers + +### 1. Register Your Application + +With your OIDC provider: +- Create a new application +- Configure redirect URIs (usually `http://localhost:8000/auth/callback` for local development) +- Get your credentials: `client_id`, `client_secret` (if applicable), and `issuer` + +### 2. Configure Environment Variables + +Each example uses environment variables for configuration. Never commit `.env` files with real credentials. + +### 3. Test the Endpoints + +All examples include: +- `/` - Public endpoint (no authentication required) +- `/protected` - Protected endpoint (requires valid OIDC token) +- `/user-info` - Returns all token claims +- `/docs` - Interactive API documentation (FastAPI automatic docs) + +## Getting Tokens for Testing + +### Option 1: Using Provider's Test Tools + +Most providers offer testing tools: +- **Okta**: Okta Dashboard → Applications → OAuth 2.0 Playground +- **Auth0**: Auth0 Dashboard → Applications → Quick Start → Test +- **Google**: Google OAuth 2.0 Playground +- **Azure AD**: Microsoft Graph Explorer + +### Option 2: Using curl + +```bash +# Example for client credentials flow (adjust for your provider) +curl -X POST "https://your-auth-server.com/oauth2/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -d "client_id=YOUR_CLIENT_ID" \ + -d "client_secret=YOUR_CLIENT_SECRET" \ + -d "scope=openid profile email" +``` + +### Option 3: Using the Interactive Docs + +1. Run the example application +2. Go to `http://localhost:8000/docs` +3. Click "Authorize" button +4. Enter your token +5. Test the protected endpoints + +## Testing Protected Endpoints + +Once you have a token: + +```bash +# Test protected endpoint +curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + http://localhost:8000/protected + +# Test user info endpoint +curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + http://localhost:8000/user-info +``` + +## Common Configuration Parameters + +All examples use these common parameters: + +| Parameter | Description | Example | +|-----------|-------------|---------| +| `OIDC_CLIENT_ID` | Your application's client ID | `0oa1e3pv9opbyq2Gm4x7` | +| `OIDC_AUDIENCE` | Token audience (often equals client_id) | `https://api.example.com` | +| `OIDC_BASE_URI` | Base URL of your auth server | `https://dev-123456.okta.com` | +| `OIDC_ISSUER` | Token issuer identifier | `dev-123456.okta.com` | +| `OIDC_CACHE_TTL` | Cache duration for signing keys (seconds) | `3600` | + +## Security Notes + +⚠️ **Important Security Practices:** + +- Never commit `.env` files or credentials to version control +- Always use HTTPS in production +- Use environment variables for all sensitive configuration +- Set appropriate cache TTL values (3600-7200 seconds recommended) +- Validate the issuer matches your authentication server +- Monitor authentication logs for suspicious activity + +## Troubleshooting + +### "Unauthorized: Signature verification failed" + +Check: +- Your `client_id` and `issuer` are correct +- The token hasn't expired +- Network connectivity to your auth server + +### "Unauthorized: Invalid audience" + +The token's `aud` claim doesn't match your configuration. Set the `OIDC_AUDIENCE` environment variable to match your token's audience. + +### "Connection timeout" + +Check: +- Network connectivity to your auth server +- Firewall rules allow outbound HTTPS +- The base URI is correct (no trailing slash) + +## Contributing Examples + +We welcome examples for additional OIDC providers! To contribute: + +1. Create a new directory under `examples/` +2. Include `main.py`, `README.md`, and `.env.example` +3. Follow the pattern from existing examples +4. Test your example thoroughly +5. Submit a pull request + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed guidelines. + +## Additional Resources + +- [fastapi-oidc Documentation](https://fastapi-oidc.readthedocs.io) +- [OpenID Connect Specification](https://openid.net/specs/openid-connect-core-1_0.html) +- [FastAPI Documentation](https://fastapi.tiangolo.com/) +- [OAuth 2.0 Security Best Practices](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics) diff --git a/examples/okta/.env.example b/examples/okta/.env.example new file mode 100644 index 0000000..94f4d3d --- /dev/null +++ b/examples/okta/.env.example @@ -0,0 +1,23 @@ +# Okta OIDC Configuration +# Copy this file to .env and fill in your actual values + +# Your Okta domain (without https://) +# Example: dev-123456.okta.com or mycompany.okta.com +OKTA_DOMAIN=dev-123456.okta.com + +# Your application's client ID from Okta +# Found in: Applications > Your App > General +OKTA_CLIENT_ID=0oa1e3pv9opbyq2Gm4x7 + +# Token audience (optional) +# If not set, defaults to CLIENT_ID +# For API applications, often: api://default +OKTA_AUDIENCE=api://default + +# Cache TTL for signing keys in seconds +# Recommended: 3600 (1 hour) +OKTA_CACHE_TTL=3600 + +# Client Secret (only needed for certain grant types) +# Keep this secret! Never commit to version control +# OKTA_CLIENT_SECRET=your_client_secret_here diff --git a/examples/okta/README.md b/examples/okta/README.md new file mode 100644 index 0000000..054025e --- /dev/null +++ b/examples/okta/README.md @@ -0,0 +1,265 @@ +# Okta OIDC Example + +This example demonstrates integrating fastapi-oidc with Okta. + +## Prerequisites + +1. An Okta account (free at [developer.okta.com](https://developer.okta.com)) +2. Python 3.10+ +3. Poetry (or pip) + +## Setup + +### 1. Configure Okta + +1. **Log in to your Okta admin console** at `https://YOUR-DOMAIN.okta.com/admin` + +2. **Create a new app integration:** + - Go to **Applications** → **Create App Integration** + - Choose **OIDC - OpenID Connect** + - Choose **Web Application** or **API Services** (for machine-to-machine) + +3. **Configure the application:** + - **App name**: "FastAPI OIDC Example" + - **Grant type**: + - ✅ Client Credentials (for machine-to-machine) + - ✅ Authorization Code (for user authentication) + - **Sign-in redirect URIs**: `http://localhost:8000/auth/callback` (if using authorization code flow) + - **Sign-out redirect URIs**: `http://localhost:8000` + - **Controlled access**: Choose appropriate access level + +4. **Save and note your credentials:** + - **Client ID**: Found on the application page + - **Client Secret**: Found on the application page (keep this secret!) + - **Okta Domain**: Your Okta domain (e.g., `dev-123456.okta.com`) + +### 2. Configure Environment Variables + +1. **Copy the example environment file:** + ```bash + cp .env.example .env + ``` + +2. **Edit `.env` with your Okta credentials:** + ```bash + OKTA_DOMAIN=dev-123456.okta.com + OKTA_CLIENT_ID=0oa1e3pv9opbyq2Gm4x7 + OKTA_AUDIENCE=api://default # or your custom audience + OKTA_CACHE_TTL=3600 + ``` + + **Finding your Okta domain:** + - It's in the top-right of your Okta admin console + - Format: `dev-XXXXXX.okta.com` or `YOURCOMPANY.okta.com` + + **About audience:** + - For API applications, use `api://default` or your custom authorization server audience + - If not specified, defaults to `client_id` + +### 3. Install Dependencies + +From the project root: +```bash +poetry install +``` + +### 4. Run the Application + +```bash +cd examples/okta +poetry run uvicorn main:app --reload +``` + +The application will be available at `http://localhost:8000`. + +## Testing + +### Get a Token from Okta + +**Option 1: Using Okta API (Client Credentials Flow)** + +```bash +curl -X POST "https://${OKTA_DOMAIN}/oauth2/default/v1/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -d "client_id=${OKTA_CLIENT_ID}" \ + -d "client_secret=${OKTA_CLIENT_SECRET}" \ + -d "scope=openid profile email" +``` + +**Option 2: Using Okta Dashboard** + +1. Go to **Security** → **API** → **Tokens** +2. Create a new token +3. Copy the token value + +**Option 3: Using Okta's OAuth 2.0 Playground** + +1. Visit your Okta org URL +2. Navigate to the OAuth 2.0 playground +3. Follow the authorization flow +4. Copy the access token + +### Test the Endpoints + +**Public endpoint (no authentication):** +```bash +curl http://localhost:8000/ +``` + +**Protected endpoint (requires authentication):** +```bash +curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + http://localhost:8000/protected +``` + +**User info endpoint:** +```bash +curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + http://localhost:8000/user-info +``` + +**Interactive API documentation:** +Visit `http://localhost:8000/docs` in your browser. + +## API Endpoints + +- `GET /` - Public endpoint, returns welcome message +- `GET /protected` - Protected endpoint, returns user email and subject +- `GET /user-info` - Protected endpoint, returns all token claims +- `GET /docs` - Interactive API documentation (FastAPI automatic docs) +- `GET /redoc` - ReDoc API documentation + +## Troubleshooting + +### "Unauthorized: Signature verification failed" + +**Causes:** +- Incorrect Okta domain +- Token expired +- Client ID mismatch + +**Solutions:** +```bash +# Verify your configuration +echo $OKTA_DOMAIN +echo $OKTA_CLIENT_ID + +# Check if Okta discovery endpoint is accessible +curl https://${OKTA_DOMAIN}/.well-known/openid-configuration + +# Decode your token to check claims (without verification) +# Visit https://jwt.io and paste your token +``` + +### "Unauthorized: Invalid audience" + +**Cause:** The token's `aud` claim doesn't match your configuration. + +**Solution:** +```bash +# Set the audience explicitly in .env +OKTA_AUDIENCE=your_actual_audience + +# Check what audience your token has +# Decode the token at https://jwt.io +``` + +### "Connection timeout" + +**Causes:** +- Network connectivity issues +- Firewall blocking HTTPS to Okta +- Incorrect domain format + +**Solutions:** +```bash +# Test connectivity +curl https://${OKTA_DOMAIN}/.well-known/openid-configuration + +# Ensure domain doesn't include https:// +# Correct: dev-123456.okta.com +# Incorrect: https://dev-123456.okta.com +``` + +### Token Expired + +Okta tokens typically expire after 1 hour. Get a new token using the methods above. + +## Okta-Specific Configuration + +### Authorization Servers + +Okta supports multiple authorization servers: + +- **Default**: Use `https://${OKTA_DOMAIN}/oauth2/default` +- **Custom**: Use `https://${OKTA_DOMAIN}/oauth2/{authServerId}` + +Update `OKTA_BASE_URI` in your `.env` if using a custom authorization server. + +### Scopes + +Common Okta scopes: +- `openid` - Required for OIDC +- `profile` - User profile information +- `email` - User email +- `offline_access` - Refresh tokens +- `groups` - User group membership + +### Custom Claims + +To add custom claims to your tokens: +1. Go to **Security** → **API** → **Authorization Servers** +2. Select your authorization server +3. Go to **Claims** tab +4. Add custom claims + +Then use a custom token model: +```python +from fastapi_oidc import IDToken + +class CustomOktaToken(IDToken): + groups: list[str] = [] + department: str = "" + +authenticate_user = get_auth(**OIDC_CONFIG, token_type=CustomOktaToken) +``` + +## Production Considerations + +### Security + +- ✅ Use HTTPS in production +- ✅ Store credentials in secure secret management (AWS Secrets Manager, Azure Key Vault, etc.) +- ✅ Rotate client secrets regularly +- ✅ Use appropriate token expiration times +- ✅ Implement rate limiting +- ✅ Monitor authentication logs in Okta + +### Performance + +- Set appropriate `OKTA_CACHE_TTL` (3600 seconds recommended) +- Consider using Okta's rate limiting best practices +- Cache frequently accessed user data at application level + +### High Availability + +- Okta has built-in high availability +- Consider fallback mechanisms for network issues +- Implement retry logic with exponential backoff + +## Additional Resources + +- [Okta Developer Documentation](https://developer.okta.com/docs/) +- [Okta API Reference](https://developer.okta.com/docs/reference/) +- [Okta OIDC & OAuth 2.0 Guide](https://developer.okta.com/docs/concepts/oauth-openid/) +- [fastapi-oidc Documentation](https://fastapi-oidc.readthedocs.io) + +## Support + +For Okta-specific issues: +- [Okta Developer Forums](https://devforum.okta.com/) +- [Okta Support](https://support.okta.com/) + +For fastapi-oidc issues: +- [GitHub Issues](https://github.com/HarryMWinters/fastapi-oidc/issues) diff --git a/examples/okta/main.py b/examples/okta/main.py new file mode 100644 index 0000000..359e434 --- /dev/null +++ b/examples/okta/main.py @@ -0,0 +1,158 @@ +"""Example FastAPI application with Okta OIDC authentication. + +This example demonstrates: +- Basic authentication with Okta +- Protected and public endpoints +- Extracting user information from tokens +- Error handling +""" + +import os + +from fastapi import Depends +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +from fastapi_oidc import IDToken +from fastapi_oidc import get_auth + +# Load configuration from environment +OKTA_DOMAIN = os.getenv("OKTA_DOMAIN", "dev-123456.okta.com") +CLIENT_ID = os.getenv("OKTA_CLIENT_ID", "your-client-id") +AUDIENCE = os.getenv("OKTA_AUDIENCE") # Optional, defaults to CLIENT_ID +CACHE_TTL = int(os.getenv("OKTA_CACHE_TTL", "3600")) + +# Configure OIDC authentication +authenticate_user = get_auth( + client_id=CLIENT_ID, + audience=AUDIENCE if AUDIENCE else CLIENT_ID, + base_authorization_server_uri=f"https://{OKTA_DOMAIN}", + issuer=OKTA_DOMAIN, + signature_cache_ttl=CACHE_TTL, +) + +# Create FastAPI application +app = FastAPI( + title="FastAPI OIDC - Okta Example", + description="Example application demonstrating Okta OIDC authentication with fastapi-oidc", + version="1.0.0", + docs_url="/docs", + redoc_url="/redoc", +) + + +@app.get("/") +def root(): + """Public endpoint - no authentication required. + + Returns: + Welcome message with application information. + """ + return { + "message": "Welcome to FastAPI OIDC Example", + "provider": "Okta", + "status": "running", + "docs": "Visit /docs for interactive API documentation", + "endpoints": { + "public": ["/"], + "protected": ["/protected", "/user-info"], + }, + } + + +@app.get("/protected") +def protected(id_token: IDToken = Depends(authenticate_user)): + """Protected endpoint - requires valid OIDC token. + + Args: + id_token: Validated ID token from Okta. + + Returns: + User information extracted from the token. + """ + return { + "message": "Successfully authenticated!", + "user": { + "email": getattr(id_token, "email", None), + "name": getattr(id_token, "name", None), + "sub": id_token.sub, + }, + "token_info": { + "issuer": id_token.iss, + "audience": id_token.aud, + "issued_at": id_token.iat, + "expires_at": id_token.exp, + }, + } + + +@app.get("/user-info") +def user_info(id_token: IDToken = Depends(authenticate_user)): + """Return all available user information from the token. + + Args: + id_token: Validated ID token from Okta. + + Returns: + Complete token contents as a dictionary. + """ + return { + "user_info": id_token.model_dump(), + "note": "This includes all claims present in your token", + } + + +@app.exception_handler(401) +def unauthorized_handler(request, exc): + """Custom handler for unauthorized requests. + + Args: + request: The incoming request. + exc: The exception that was raised. + + Returns: + JSON response with error details. + """ + return JSONResponse( + status_code=401, + content={ + "error": "Unauthorized", + "message": "Invalid or missing authentication token", + "hint": "Include 'Authorization: Bearer ' header with a valid Okta OIDC token", + "docs": "See https://fastapi-oidc.readthedocs.io for more information", + }, + ) + + +@app.exception_handler(403) +def forbidden_handler(request, exc): + """Custom handler for forbidden requests. + + Args: + request: The incoming request. + exc: The exception that was raised. + + Returns: + JSON response with error details. + """ + return JSONResponse( + status_code=403, + content={ + "error": "Forbidden", + "message": "Authentication required to access this resource", + "hint": "Obtain a token from Okta and include it in the Authorization header", + }, + ) + + +if __name__ == "__main__": + import uvicorn + + print("Starting FastAPI OIDC Example with Okta") + print(f"Okta Domain: {OKTA_DOMAIN}") + print(f"Client ID: {CLIENT_ID}") + print(f"Audience: {AUDIENCE if AUDIENCE else CLIENT_ID}") + print(f"Cache TTL: {CACHE_TTL} seconds") + print("\nVisit http://localhost:8000/docs for interactive API documentation") + + uvicorn.run(app, host="0.0.0.0", port=8000) # nosec B104 diff --git a/fastapi_oidc/__init__.py b/fastapi_oidc/__init__.py index 8e90fe3..beb0ca0 100644 --- a/fastapi_oidc/__init__.py +++ b/fastapi_oidc/__init__.py @@ -1,3 +1,29 @@ -from fastapi_oidc.auth import get_auth # noqa -from fastapi_oidc.types import IDToken # noqa -from fastapi_oidc.types import OktaIDToken # noqa +"""FastAPI OIDC - Verify and decrypt 3rd party OIDC ID tokens. + +This package provides utilities for validating OIDC ID tokens issued by +third-party authentication servers in FastAPI applications. + +Example: + >>> from fastapi_oidc import get_auth, IDToken + >>> from fastapi import Depends, FastAPI + >>> + >>> authenticate_user = get_auth( + ... client_id="your-client-id", + ... base_authorization_server_uri="https://auth.example.com", + ... issuer="auth.example.com", + ... signature_cache_ttl=3600, + ... ) + >>> + >>> app = FastAPI() + >>> + >>> @app.get("/protected") + >>> def protected(token: IDToken = Depends(authenticate_user)): + ... return {"user": token.email} +""" + +from fastapi_oidc.auth import get_auth +from fastapi_oidc.types import IDToken +from fastapi_oidc.types import OktaIDToken + +__all__ = ["get_auth", "IDToken", "OktaIDToken"] +__version__ = "0.0.11" diff --git a/fastapi_oidc/auth.py b/fastapi_oidc/auth.py index dc0b28b..183cee4 100644 --- a/fastapi_oidc/auth.py +++ b/fastapi_oidc/auth.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- """ -Module for validating OIDC ID Tokens. Configured via config.py +Module for validating OIDC ID Tokens. Configured via config.py Usage ===== @@ -44,7 +43,7 @@ def get_auth( ) -> Callable[[str], IDToken]: """Take configurations and return the authenticate_user function. - This function should only be invoked once at the beggining of your + This function should only be invoked once at the beginning of your server code. The function it returns should be used to check user credentials. Args: diff --git a/fastapi_oidc/discovery.py b/fastapi_oidc/discovery.py index 916d2c9..b45aa4e 100644 --- a/fastapi_oidc/discovery.py +++ b/fastapi_oidc/discovery.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Any import requests from cachetools import TTLCache @@ -6,27 +6,75 @@ def configure(*_, cache_ttl: int): + """Configure OIDC discovery functions with caching. + + This factory function creates a set of cached discovery functions + for retrieving OIDC server configuration, public keys, and signing + algorithms. All functions are cached using TTL-based caching. + + Args: + cache_ttl: Time-to-live for cached values in seconds. + + Returns: + A functions namespace object with three methods: + - auth_server: Discover OIDC server configuration + - public_keys: Retrieve public signing keys + - signing_algos: Get supported signing algorithms + + Example: + >>> discover = configure(cache_ttl=3600) + >>> config = discover.auth_server(base_url="https://auth.example.com") + """ + @cached(TTLCache(1, cache_ttl), key=lambda d: d["jwks_uri"]) - def get_authentication_server_public_keys(OIDC_spec: Dict): - """ - Retrieve the public keys used by the authentication server - for signing OIDC ID tokens. + def get_authentication_server_public_keys( + OIDC_spec: dict[str, Any] + ) -> dict[str, Any]: + """Retrieve the public keys used by the authentication server. + + Args: + OIDC_spec: The OIDC discovery document containing the jwks_uri. + + Returns: + Dictionary containing the public keys in JWKS format. + + Raises: + requests.RequestException: If the request to fetch keys fails. """ keys_uri = OIDC_spec["jwks_uri"] r = requests.get(keys_uri, timeout=15) keys = r.json() return keys - def get_signing_algos(OIDC_spec: Dict): + def get_signing_algos(OIDC_spec: dict[str, Any]) -> list[str]: + """Extract the supported signing algorithms from OIDC spec. + + Args: + OIDC_spec: The OIDC discovery document. + + Returns: + List of supported signing algorithm identifiers. + """ algos = OIDC_spec["id_token_signing_alg_values_supported"] return algos @cached(TTLCache(1, cache_ttl)) - def discover_auth_server(*_, base_url: str) -> Dict: + def discover_auth_server(*_, base_url: str) -> dict[str, Any]: + """Discover OIDC server configuration via well-known endpoint. + + Args: + base_url: Base URL of the authorization server. + + Returns: + Dictionary containing the OIDC server configuration. + + Raises: + requests.HTTPError: If the discovery endpoint returns an error. + requests.RequestException: If the network request fails. + """ discovery_url = f"{base_url}/.well-known/openid-configuration" r = requests.get(discovery_url, timeout=15) - # If the auth server is failing we can't verify tokens. - # Soooo panic I guess? + # If the auth server is failing, token verification is impossible r.raise_for_status() configuration = r.json() return configuration diff --git a/fastapi_oidc/exceptions.py b/fastapi_oidc/exceptions.py index 130efe1..c6bc7e6 100644 --- a/fastapi_oidc/exceptions.py +++ b/fastapi_oidc/exceptions.py @@ -1,2 +1,8 @@ -class TokenSpecificationError(BaseException): +class TokenSpecificationError(Exception): + """Raised when an invalid token type is provided to get_auth(). + + This exception indicates a programming error where the token_type + parameter is not a subclass of IDToken. + """ + pass diff --git a/fastapi_oidc/py.typed b/fastapi_oidc/py.typed index 7ffcf83..e5f355b 100644 --- a/fastapi_oidc/py.typed +++ b/fastapi_oidc/py.typed @@ -1 +1 @@ -# This file intentionally left blank to mark the package as typed (PEP 561). \ No newline at end of file +# This file intentionally left blank to mark the package as typed (PEP 561). diff --git a/fastapi_oidc/types.py b/fastapi_oidc/types.py index 8ad3ebb..4228c7e 100644 --- a/fastapi_oidc/types.py +++ b/fastapi_oidc/types.py @@ -1,5 +1,3 @@ -from typing import List - from pydantic import BaseModel from pydantic import ConfigDict @@ -44,7 +42,7 @@ class OktaIDToken(IDToken): auth_time: int ver: int jti: str - amr: List[str] + amr: list[str] idp: str nonce: str at_hash: str diff --git a/poetry.lock b/poetry.lock index ea04ff9..a301a8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -6,6 +6,7 @@ version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, @@ -17,6 +18,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -28,6 +30,7 @@ version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, @@ -41,67 +44,23 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.23)"] -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = "*" -files = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] - [[package]] name = "astroid" -version = "2.15.8" +version = "3.3.11" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.9.0" +groups = ["dev"] files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, + {file = "astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec"}, + {file = "astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce"}, ] [package.dependencies] -lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] - -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [[package]] name = "babel" @@ -109,6 +68,7 @@ version = "2.15.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, @@ -119,26 +79,50 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "19.10b0" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.6" -files = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cachetools" @@ -146,6 +130,7 @@ version = "5.4.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, @@ -157,6 +142,7 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -168,6 +154,8 @@ version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, @@ -232,6 +220,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -243,6 +232,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "dev"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -342,6 +332,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -356,17 +347,128 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.13.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, + {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, + {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, + {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, + {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, + {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, + {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, + {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, + {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, + {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, + {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, + {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, + {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, + {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, + {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, + {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, + {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, + {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, + {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, + {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, + {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, + {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, + {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + [[package]] name = "cryptography" version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, @@ -416,6 +518,7 @@ version = "0.3.8" description = "serialize all of Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, @@ -431,6 +534,7 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -442,6 +546,7 @@ version = "2.6.1" description = "DNS toolkit" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -458,13 +563,14 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "docutils" -version = "0.16" +version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] @@ -473,6 +579,7 @@ version = "0.19.0" description = "ECDSA cryptographic signature library (pure python)" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" +groups = ["main"] files = [ {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, @@ -491,6 +598,7 @@ version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, @@ -506,6 +614,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -520,6 +630,7 @@ version = "0.111.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastapi-0.111.1-py3-none-any.whl", hash = "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf"}, {file = "fastapi-0.111.1.tar.gz", hash = "sha256:ddd1ac34cb1f76c2e2d7f8545a4bcb5463bce4834e81abf0b189e0c359ab2413"}, @@ -545,6 +656,7 @@ version = "0.0.4" description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"}, {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"}, @@ -562,6 +674,7 @@ version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, @@ -570,7 +683,7 @@ files = [ [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[package]] name = "h11" @@ -578,6 +691,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -589,6 +703,7 @@ version = "1.0.5" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, @@ -610,6 +725,7 @@ version = "0.6.1" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, @@ -658,6 +774,7 @@ version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, @@ -671,7 +788,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -682,6 +799,7 @@ version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, @@ -696,6 +814,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -707,6 +826,7 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -718,6 +838,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -729,6 +850,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -743,6 +865,7 @@ version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, @@ -755,49 +878,90 @@ MarkupSafe = ">=2.0" i18n = ["Babel (>=2.7)"] [[package]] -name = "lazy-object-proxy" -version = "1.10.0" -description = "A fast and thorough lazy object proxy." +name = "librt" +version = "0.7.8" +description = "Mypyc runtime library" optional = false -python-versions = ">=3.8" -files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d"}, + {file = "librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b"}, + {file = "librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d"}, + {file = "librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d"}, + {file = "librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c"}, + {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c"}, + {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d"}, + {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0"}, + {file = "librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85"}, + {file = "librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c"}, + {file = "librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f"}, + {file = "librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac"}, + {file = "librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c"}, + {file = "librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8"}, + {file = "librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff"}, + {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3"}, + {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75"}, + {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873"}, + {file = "librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7"}, + {file = "librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c"}, + {file = "librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232"}, + {file = "librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63"}, + {file = "librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93"}, + {file = "librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592"}, + {file = "librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850"}, + {file = "librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62"}, + {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b"}, + {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714"}, + {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449"}, + {file = "librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac"}, + {file = "librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708"}, + {file = "librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0"}, + {file = "librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc"}, + {file = "librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2"}, + {file = "librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3"}, + {file = "librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6"}, + {file = "librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d"}, + {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e"}, + {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca"}, + {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93"}, + {file = "librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951"}, + {file = "librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34"}, + {file = "librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09"}, + {file = "librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418"}, + {file = "librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611"}, + {file = "librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758"}, + {file = "librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea"}, + {file = "librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac"}, + {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398"}, + {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81"}, + {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83"}, + {file = "librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d"}, + {file = "librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44"}, + {file = "librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce"}, + {file = "librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f"}, + {file = "librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde"}, + {file = "librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e"}, + {file = "librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b"}, + {file = "librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666"}, + {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581"}, + {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a"}, + {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca"}, + {file = "librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365"}, + {file = "librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32"}, + {file = "librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06"}, + {file = "librt-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6"}, + {file = "librt-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b"}, + {file = "librt-0.7.8-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c"}, + {file = "librt-0.7.8-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5"}, + {file = "librt-0.7.8-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71"}, + {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e"}, + {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63"}, + {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94"}, + {file = "librt-0.7.8-cp39-cp39-win32.whl", hash = "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb"}, + {file = "librt-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be"}, + {file = "librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862"}, ] [[package]] @@ -806,6 +970,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -830,6 +995,7 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -899,6 +1065,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -910,6 +1077,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -917,53 +1085,76 @@ files = [ [[package]] name = "mypy" -version = "0.910" +version = "1.19.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typing-extensions = ">=3.7.4" +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "0.4.4" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=2.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] @@ -972,6 +1163,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -983,6 +1175,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -994,6 +1187,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1005,6 +1199,7 @@ version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, @@ -1021,6 +1216,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1032,13 +1228,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.21.0" +version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, ] [package.dependencies] @@ -1048,23 +1245,13 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - [[package]] name = "pyasn1" version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, @@ -1076,6 +1263,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1087,6 +1276,7 @@ version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, @@ -1096,8 +1286,8 @@ files = [ annotated-types = ">=0.4.0" pydantic-core = "2.20.1" typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, ] [package.extras] @@ -1109,6 +1299,7 @@ version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, @@ -1210,6 +1401,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -1220,42 +1412,46 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "1.7.1" +version = "2.10.1" description = "JSON Web Token implementation in Python" optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, - {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"}, + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] [package.extras] -crypto = ["cryptography (>=1.4)"] -flake8 = ["flake8", "flake8-import-order", "pep8-naming"] -test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "2.17.7" +version = "3.3.9" description = "python code static checker" optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.9.0" +groups = ["dev"] files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, + {file = "pylint-3.3.9-py3-none-any.whl", hash = "sha256:01f9b0462c7730f94786c283f3e52a1fbdf0494bbe0971a78d7277ef46a751e7"}, + {file = "pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a"}, ] [package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" +astroid = ">=3.3.8,<=3.4.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.3.6", markers = "python_version == \"3.11\""}, ] -isort = ">=4.2.5,<6" +isort = ">=4.2.5,<5.13 || >5.13,<7" mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +platformdirs = ">=2.2" +tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" [package.extras] @@ -1264,27 +1460,46 @@ testutils = ["gitpython (>3)"] [[package]] name = "pytest" -version = "6.2.5" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "python-dotenv" @@ -1292,6 +1507,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1306,6 +1522,7 @@ version = "3.3.0" description = "JOSE implementation in Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, @@ -1328,6 +1545,7 @@ version = "0.0.18" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, @@ -1339,6 +1557,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -1393,100 +1612,13 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -[[package]] -name = "regex" -version = "2024.7.24" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -files = [ - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, - {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, - {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, - {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, - {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, - {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, - {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, - {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, - {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, - {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, - {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, - {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, -] - [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1508,6 +1640,7 @@ version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, @@ -1526,6 +1659,7 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = false python-versions = ">=3.6,<4" +groups = ["main"] files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -1534,28 +1668,13 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" -[[package]] -name = "setuptools" -version = "72.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, - {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, -] - -[package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "shellingham" version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -1567,6 +1686,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1578,6 +1698,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1589,6 +1710,7 @@ version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, @@ -1596,38 +1718,39 @@ files = [ [[package]] name = "sphinx" -version = "3.5.4" +version = "7.4.7" description = "Python documentation generator" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"}, - {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"}, + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12,<0.17" -imagesize = "*" -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" -requests = ">=2.5.0" -setuptools = "*" -snowballstemmer = ">=1.1" +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.800)"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinxcontrib-applehelp" @@ -1635,6 +1758,7 @@ version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -1651,6 +1775,7 @@ version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -1667,6 +1792,7 @@ version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -1683,6 +1809,7 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -1697,6 +1824,7 @@ version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -1713,6 +1841,7 @@ version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -1729,6 +1858,7 @@ version = "0.37.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, @@ -1740,23 +1870,14 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1768,67 +1889,19 @@ version = "0.13.0" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, ] -[[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, - {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, -] - [[package]] name = "typer" version = "0.12.3" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, @@ -1846,6 +1919,7 @@ version = "0.1.10" description = "Typing stubs for cachetools" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "types-cachetools-0.1.10.tar.gz", hash = "sha256:f2727cf379989ba4d081f9ed11ec28c6730adbd120dc0deb2036c5ef69e515e0"}, {file = "types_cachetools-0.1.10-py3-none-any.whl", hash = "sha256:76d312d6e55fe19f7a2ea9a3271945e6f5c56f4fd76cc3cb2366109785f3462b"}, @@ -1857,6 +1931,7 @@ version = "2.32.0.20240712" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, @@ -1871,6 +1946,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1882,13 +1958,14 @@ version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1899,6 +1976,7 @@ version = "0.30.3" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"}, {file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"}, @@ -1912,12 +1990,12 @@ httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standar python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -1925,6 +2003,8 @@ version = "0.19.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" files = [ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, @@ -1961,7 +2041,7 @@ files = [ [package.extras] docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0) ; python_version >= \"3.12\"", "aiohttp (>=3.8.1) ; python_version < \"3.12\"", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] [[package]] name = "virtualenv" @@ -1969,6 +2049,7 @@ version = "20.26.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, @@ -1981,7 +2062,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchfiles" @@ -1989,6 +2070,7 @@ version = "0.22.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, @@ -2076,6 +2158,7 @@ version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, @@ -2151,86 +2234,7 @@ files = [ {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, ] -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "6ab5b171507418ea92993757d2d9181994e471bea7938e52b6a092e7f11a99b6" +content-hash = "7af4c53a2b2899d93dee9fa86a945ee33dd52c4c2e0d2f0313b16fc95a052c27" diff --git a/pyproject.toml b/pyproject.toml index b577b57..0d1fdaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,23 +18,48 @@ cachetools = ">= 4.1.1" requests = ">= 2.24.0" python-jose = {extras = ["cryptography"], version = ">= 3.2.0"} -[tool.poetry.dev-dependencies] -pytest = "^6.0.1" -black = "^19.10b0" -pylint = "^2.6.0" -pyjwt = "^1.7.1" -sphinx = "^3.3.1" -mypy = "^0.910" +[tool.poetry.group.dev.dependencies] +pytest = "^8.0.0" +pytest-cov = "^5.0.0" +black = "^24.0.0" +pylint = "^3.0.0" +pyjwt = "^2.0.0" +sphinx = "^7.0.0" +mypy = "^1.11.0" types-cachetools = "^0.1.9" types-requests = "^2.25.0" -pre-commit = "^2.13.0" +pre-commit = "^3.0.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" [tool.isort] profile = "black" force_single_line = "True" known_first_party = [] -known_third_party = ["cachetools", "cryptography", "fastapi", "jose", "jwt", "pydantic", "pytest", "requests"] \ No newline at end of file +known_third_party = ["cachetools", "cryptography", "fastapi", "jose", "jwt", "pydantic", "pytest", "requests"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = [ + "-v", + "--strict-markers", + "--cov=fastapi_oidc", + "--cov-report=term-missing", + "--cov-report=xml", + "--cov-fail-under=80", +] + +[tool.coverage.run] +source = ["fastapi_oidc"] +omit = ["*/tests/*"] +branch = true + +[tool.coverage.report] +precision = 2 +show_missing = true +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", +] diff --git a/tests/conftest.py b/tests/conftest.py index 5496e75..0b727c9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import json -import os import time import uuid +from pathlib import Path import jwt import pytest @@ -9,7 +9,7 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -FIXTURES_DIRECTORY = os.path.join(os.path.dirname(__file__), "fixtures") +FIXTURES_DIRECTORY = Path(__file__).parent / "fixtures" KEY = rsa.generate_private_key( @@ -19,7 +19,7 @@ @pytest.fixture def oidc_discovery(): - with open(FIXTURES_DIRECTORY + "/AuthServerDiscovery.json") as f: + with open(FIXTURES_DIRECTORY / "AuthServerDiscovery.json") as f: OIDC_DISCOVERY_RESPONSE = json.load(f) return OIDC_DISCOVERY_RESPONSE @@ -101,7 +101,7 @@ def token_with_audience(private_key, config_w_aud, test_email) -> str: }, private_key, algorithm="RS256", - ).decode("UTF-8") + ) @pytest.fixture @@ -131,7 +131,7 @@ def token_without_audience(private_key, no_audience_config, test_email) -> str: }, private_key, algorithm="RS256", - ).decode("UTF-8") + ) @pytest.fixture diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py new file mode 100644 index 0000000..e38722c --- /dev/null +++ b/tests/test_edge_cases.py @@ -0,0 +1,323 @@ +"""Test edge cases and error conditions.""" + +import time +import uuid +from unittest.mock import Mock +from unittest.mock import patch + +import jwt +import pytest +import requests + +from fastapi_oidc import discovery +from fastapi_oidc.auth import get_auth +from fastapi_oidc.exceptions import TokenSpecificationError +from fastapi_oidc.types import IDToken + + +def test_discovery_handles_network_timeout(): + """Test that discovery handles network timeouts gracefully.""" + with patch("requests.get") as mock_get: + mock_get.side_effect = requests.Timeout("Connection timeout") + + discover = discovery.configure(cache_ttl=100) + + with pytest.raises(requests.Timeout): + discover.auth_server(base_url="https://example.com") + + +def test_discovery_handles_http_error(): + """Test that discovery handles HTTP errors.""" + with patch("requests.get") as mock_get: + mock_response = Mock() + mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found") + mock_get.return_value = mock_response + + discover = discovery.configure(cache_ttl=100) + + with pytest.raises(requests.HTTPError): + discover.auth_server(base_url="https://example.com") + + +def test_discovery_handles_connection_error(): + """Test that discovery handles connection errors.""" + with patch("requests.get") as mock_get: + mock_get.side_effect = requests.ConnectionError( + "Failed to establish connection" + ) + + discover = discovery.configure(cache_ttl=100) + + with pytest.raises(requests.ConnectionError): + discover.auth_server(base_url="https://example.com") + + +def test_get_auth_rejects_non_idtoken_subclass(): + """Test that get_auth validates token_type is IDToken subclass.""" + + class NotAnIDToken: + pass + + config = { + "client_id": "test", + "base_authorization_server_uri": "https://example.com", + "issuer": "example.com", + "signature_cache_ttl": 3600, + } + + with pytest.raises(TokenSpecificationError) as exc_info: + get_auth(**config, token_type=NotAnIDToken) + + assert "must be a subclass of" in str(exc_info.value) + assert "NotAnIDToken" in str(exc_info.value) + + +def test_get_auth_accepts_idtoken_subclass(): + """Test that get_auth accepts valid IDToken subclasses.""" + + class ValidToken(IDToken): + custom_field: str = "test" + + config = { + "client_id": "test", + "base_authorization_server_uri": "https://example.com", + "issuer": "example.com", + "signature_cache_ttl": 3600, + } + + # Should not raise an exception + result = get_auth(**config, token_type=ValidToken) + assert callable(result) + + +def test_multiple_audience_values( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test handling tokens with multiple audience values.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + # Token with array of audiences + multi_aud_token = jwt.encode( + { + "aud": ["audience1", "audience2", config_w_aud["audience"]], + "iss": config_w_aud["issuer"], + "email": test_email, + "sub": "test-sub", + "exp": now + 300, + "iat": now, + "auth_time": now, + "ver": "1", + "jti": str(uuid.uuid4()), + "amr": [], + "idp": "", + "nonce": "", + "at_hash": "", + }, + private_key, + algorithm="RS256", + ) + + authenticate_user = get_auth(**config_w_aud) + result = authenticate_user(auth_header=f"Bearer {multi_aud_token}") + + assert result.email == test_email + assert config_w_aud["audience"] in result.aud + + +def test_single_audience_as_string( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test handling tokens with single audience as string.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + single_aud_token = jwt.encode( + { + "aud": config_w_aud["audience"], # String, not list + "iss": config_w_aud["issuer"], + "email": test_email, + "sub": "test-sub", + "exp": now + 300, + "iat": now, + "auth_time": now, + "ver": "1", + "jti": str(uuid.uuid4()), + "amr": [], + "idp": "", + "nonce": "", + "at_hash": "", + }, + private_key, + algorithm="RS256", + ) + + authenticate_user = get_auth(**config_w_aud) + result = authenticate_user(auth_header=f"Bearer {single_aud_token}") + + assert result.email == test_email + assert result.aud == config_w_aud["audience"] + + +def test_token_with_extra_fields( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test that tokens with extra fields are handled correctly.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + token_with_extras = jwt.encode( + { + "aud": config_w_aud["audience"], + "iss": config_w_aud["issuer"], + "email": test_email, + "sub": "test-sub", + "exp": now + 300, + "iat": now, + # Extra fields not in IDToken model + "custom_claim_1": "value1", + "custom_claim_2": 12345, + "custom_claim_3": {"nested": "data"}, + }, + private_key, + algorithm="RS256", + ) + + authenticate_user = get_auth(**config_w_aud) + result = authenticate_user(auth_header=f"Bearer {token_with_extras}") + + # Base claims should work + assert result.email == test_email + assert result.sub == "test-sub" + + # Extra fields should be accessible (Pydantic extra="allow") + assert hasattr(result, "custom_claim_1") + assert result.custom_claim_1 == "value1" + + +def test_wrong_issuer( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test that tokens with wrong issuer are rejected.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + wrong_issuer_token = jwt.encode( + { + "aud": config_w_aud["audience"], + "iss": "wrong-issuer.com", # Wrong issuer + "email": test_email, + "sub": "test-sub", + "exp": now + 300, + "iat": now, + "auth_time": now, + "ver": "1", + "jti": str(uuid.uuid4()), + "amr": [], + "idp": "", + "nonce": "", + "at_hash": "", + }, + private_key, + algorithm="RS256", + ) + + authenticate_user = get_auth(**config_w_aud) + + with pytest.raises(Exception): # Should raise HTTPException or JWTError + authenticate_user(auth_header=f"Bearer {wrong_issuer_token}") + + +def test_wrong_audience( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test that tokens with wrong audience are rejected.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + wrong_audience_token = jwt.encode( + { + "aud": "wrong-audience", # Wrong audience + "iss": config_w_aud["issuer"], + "email": test_email, + "sub": "test-sub", + "exp": now + 300, + "iat": now, + "auth_time": now, + "ver": "1", + "jti": str(uuid.uuid4()), + "amr": [], + "idp": "", + "nonce": "", + "at_hash": "", + }, + private_key, + algorithm="RS256", + ) + + authenticate_user = get_auth(**config_w_aud) + + with pytest.raises(Exception): # Should raise HTTPException or JWTClaimsError + authenticate_user(auth_header=f"Bearer {wrong_audience_token}") + + +def test_token_about_to_expire( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test tokens that are about to expire but still valid.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + # Token expires in 1 second + almost_expired_token = jwt.encode( + { + "aud": config_w_aud["audience"], + "iss": config_w_aud["issuer"], + "email": test_email, + "sub": "test-sub", + "exp": now + 1, # Expires in 1 second + "iat": now, + "auth_time": now, + "ver": "1", + "jti": str(uuid.uuid4()), + "amr": [], + "idp": "", + "nonce": "", + "at_hash": "", + }, + private_key, + algorithm="RS256", + ) + + authenticate_user = get_auth(**config_w_aud) + # Should still be valid + result = authenticate_user(auth_header=f"Bearer {almost_expired_token}") + assert result.email == test_email + + +def test_bearer_token_extraction( + monkeypatch, mock_discovery, token_with_audience, config_w_aud +): + """Test that bearer token is correctly extracted from auth header.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + authenticate_user = get_auth(**config_w_aud) + + # Test with 'Bearer ' prefix + result = authenticate_user(auth_header=f"Bearer {token_with_audience}") + assert result is not None + + # Test with just the token (should still work due to split logic) + result = authenticate_user(auth_header=token_with_audience) + assert result is not None + + +def test_token_specification_error_inherits_from_exception(): + """Test that TokenSpecificationError correctly inherits from Exception.""" + error = TokenSpecificationError("test error") + + # Should be catchable as Exception + assert isinstance(error, Exception) + + # Should not be BaseException only + assert type(error).__bases__[0] == Exception diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..05d1c96 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,231 @@ +"""Integration tests using FastAPI TestClient.""" + +import time +import uuid + +import jwt +from fastapi import Depends +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from fastapi_oidc import IDToken +from fastapi_oidc import get_auth + + +def test_integration_with_fastapi_app( + monkeypatch, mock_discovery, token_with_audience, config_w_aud +): + """Test full integration with a FastAPI application.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + # Create FastAPI app with authentication + app = FastAPI() + authenticate_user = get_auth(**config_w_aud) + + @app.get("/protected") + def protected_endpoint(token: IDToken = Depends(authenticate_user)): + return {"email": getattr(token, "email", None), "sub": token.sub} + + @app.get("/public") + def public_endpoint(): + return {"message": "public"} + + # Test with TestClient + client = TestClient(app) + + # Test public endpoint (no auth required) + response = client.get("/public") + assert response.status_code == 200 + assert response.json() == {"message": "public"} + + # Test protected endpoint with valid token + response = client.get( + "/protected", headers={"Authorization": f"Bearer {token_with_audience}"} + ) + assert response.status_code == 200 + data = response.json() + assert "email" in data + assert "sub" in data + + # Test protected endpoint without token + response = client.get("/protected") + assert response.status_code == 403 # OpenIdConnect requires auth + + +def test_integration_custom_token_type( + monkeypatch, mock_discovery, token_with_audience, config_w_aud +): + """Test integration with custom token type.""" + + class CustomToken(IDToken): + custom_field: str = "default_value" + + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + app = FastAPI() + authenticate_user = get_auth(**config_w_aud, token_type=CustomToken) + + @app.get("/custom") + def custom_endpoint(token: CustomToken = Depends(authenticate_user)): + return { + "custom_field": token.custom_field, + "email": getattr(token, "email", None), + } + + client = TestClient(app) + response = client.get( + "/custom", headers={"Authorization": f"Bearer {token_with_audience}"} + ) + assert response.status_code == 200 + assert "custom_field" in response.json() + + +def test_integration_expired_token( + monkeypatch, mock_discovery, config_w_aud, test_email, private_key +): + """Test that expired tokens are rejected.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + # Create an expired token (exp in the past) + now = int(time.time()) + expired_token = jwt.encode( + { + "aud": config_w_aud["audience"], + "iss": config_w_aud["issuer"], + "email": test_email, + "sub": "test-sub", + "exp": now - 100, # Expired 100 seconds ago + "iat": now - 200, + "auth_time": now - 200, + "ver": "1", + "jti": str(uuid.uuid4()), + "amr": [], + "idp": "", + "nonce": "", + "at_hash": "", + }, + private_key, + algorithm="RS256", + ) + + app = FastAPI() + authenticate_user = get_auth(**config_w_aud) + + @app.get("/protected") + def protected(token: IDToken = Depends(authenticate_user)): + return {"email": getattr(token, "email", None)} + + client = TestClient(app) + response = client.get( + "/protected", headers={"Authorization": f"Bearer {expired_token}"} + ) + assert response.status_code == 401 + assert "Unauthorized" in response.json()["detail"] + + +def test_integration_invalid_token(monkeypatch, mock_discovery, config_w_aud): + """Test that malformed tokens are rejected.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + app = FastAPI() + authenticate_user = get_auth(**config_w_aud) + + @app.get("/protected") + def protected(token: IDToken = Depends(authenticate_user)): + return {"email": getattr(token, "email", None)} + + client = TestClient(app) + + # Test with invalid token + response = client.get( + "/protected", headers={"Authorization": "Bearer invalid.jwt.token"} + ) + assert response.status_code == 401 + + +def test_integration_missing_required_claims( + monkeypatch, mock_discovery, config_w_aud, private_key +): + """Test that tokens missing required claims are rejected.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + now = int(time.time()) + # Token missing 'sub' claim + incomplete_token = jwt.encode( + { + "aud": config_w_aud["audience"], + "iss": config_w_aud["issuer"], + "email": "test@example.com", + # Missing 'sub' field + "exp": now + 300, + "iat": now, + }, + private_key, + algorithm="RS256", + ) + + app = FastAPI() + authenticate_user = get_auth(**config_w_aud) + + @app.get("/protected") + def protected(token: IDToken = Depends(authenticate_user)): + return {"sub": token.sub} + + client = TestClient(app, raise_server_exceptions=False) + response = client.get( + "/protected", headers={"Authorization": f"Bearer {incomplete_token}"} + ) + # Should fail validation due to missing required field + # ValidationError results in 500 internal server error in this test context + assert response.status_code == 500 + + +def test_integration_multiple_endpoints( + monkeypatch, mock_discovery, token_with_audience, config_w_aud +): + """Test application with multiple protected endpoints.""" + monkeypatch.setattr("fastapi_oidc.auth.discovery.configure", mock_discovery) + + app = FastAPI() + authenticate_user = get_auth(**config_w_aud) + + @app.get("/public") + def public(): + return {"access": "public"} + + @app.get("/profile") + def profile(token: IDToken = Depends(authenticate_user)): + return {"sub": token.sub} + + @app.get("/settings") + def settings(token: IDToken = Depends(authenticate_user)): + return {"email": getattr(token, "email", None)} + + @app.post("/update") + def update(token: IDToken = Depends(authenticate_user)): + return {"updated": True} + + client = TestClient(app) + + # Public endpoint works without auth + response = client.get("/public") + assert response.status_code == 200 + + # All protected endpoints work with valid token + headers = {"Authorization": f"Bearer {token_with_audience}"} + + response = client.get("/profile", headers=headers) + assert response.status_code == 200 + + response = client.get("/settings", headers=headers) + assert response.status_code == 200 + + response = client.post("/update", headers=headers) + assert response.status_code == 200 + + # Protected endpoints fail without token + response = client.get("/profile") + assert response.status_code == 403 + + response = client.post("/update") + assert response.status_code == 403