(Feat): Initial Commit.
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
htmlcov/
|
||||||
|
scripts/
|
||||||
|
.vscode/
|
||||||
|
.pytest_cache
|
||||||
|
.qodo
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.10
|
||||||
221
CONTRIBUTING.md
Normal file
221
CONTRIBUTING.md
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
# Contributing to FastAPI Route Loader
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to fastapi-route-loader! This document provides guidelines and instructions for contributing.
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/fastapi-route-loader.git
|
||||||
|
cd fastapi-route-loader
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Set up the development environment**
|
||||||
|
```bash
|
||||||
|
# Run the setup script
|
||||||
|
bash scripts/setup.sh
|
||||||
|
|
||||||
|
# Or manually:
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify the setup**
|
||||||
|
```bash
|
||||||
|
bash scripts/verify.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
pytest --cov=fastapi_route_loader --cov-report=html
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
pytest tests/test_container.py
|
||||||
|
|
||||||
|
# Run specific test
|
||||||
|
pytest tests/test_container.py::TestRouterContainer::test_add_router
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
We use strict linting and type checking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run ruff linter
|
||||||
|
ruff check .
|
||||||
|
|
||||||
|
# Auto-fix issues
|
||||||
|
ruff check --fix .
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
ruff format .
|
||||||
|
|
||||||
|
# Run type checker
|
||||||
|
pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Style Guidelines
|
||||||
|
|
||||||
|
- **Type Hints**: All functions must have complete type hints
|
||||||
|
- **Docstrings**: All public functions and classes must have docstrings
|
||||||
|
- **Line Length**: Maximum 88 characters (enforced by ruff)
|
||||||
|
- **Imports**: Use absolute imports, organized by ruff
|
||||||
|
- **Python Version**: Code must be compatible with Python 3.10+
|
||||||
|
|
||||||
|
### Commit Messages
|
||||||
|
|
||||||
|
Follow conventional commit format:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
Types:
|
||||||
|
- `feat`: New feature
|
||||||
|
- `fix`: Bug fix
|
||||||
|
- `docs`: Documentation changes
|
||||||
|
- `test`: Test changes
|
||||||
|
- `refactor`: Code refactoring
|
||||||
|
- `style`: Code style changes
|
||||||
|
- `chore`: Build/tooling changes
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
feat(container): add support for router priorities
|
||||||
|
|
||||||
|
Add ability to set priority levels for routers to control
|
||||||
|
loading order.
|
||||||
|
|
||||||
|
Closes #123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pull Request Process
|
||||||
|
|
||||||
|
1. **Create a feature branch**
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Make your changes**
|
||||||
|
- Write tests for new functionality
|
||||||
|
- Update documentation as needed
|
||||||
|
- Ensure all tests pass
|
||||||
|
- Ensure code passes linting and type checking
|
||||||
|
|
||||||
|
3. **Run verification**
|
||||||
|
```bash
|
||||||
|
bash scripts/verify.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Commit your changes**
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: your feature description"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Push to your fork**
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Create a Pull Request**
|
||||||
|
- Provide a clear description of the changes
|
||||||
|
- Reference any related issues
|
||||||
|
- Ensure CI checks pass
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
### Writing Tests
|
||||||
|
|
||||||
|
- Place tests in the `tests/` directory
|
||||||
|
- Name test files as `test_*.py`
|
||||||
|
- Use descriptive test names: `test_<functionality>_<scenario>`
|
||||||
|
- Group related tests in classes
|
||||||
|
- Use fixtures for common setup
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
class TestRouterContainer:
|
||||||
|
def test_add_router_success(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
container.add_router("test", router)
|
||||||
|
assert container.has_router("test")
|
||||||
|
|
||||||
|
def test_add_router_duplicate_raises_error(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
container.add_router("test", router)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
container.add_router("test", router)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
|
||||||
|
- Aim for >90% code coverage
|
||||||
|
- Test both success and error cases
|
||||||
|
- Test edge cases and boundary conditions
|
||||||
|
- Test integration scenarios
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### Docstring Format
|
||||||
|
|
||||||
|
Use Google-style docstrings:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def add_router(self, name: str, router: APIRouter, metadata: dict[str, Any] | None = None) -> None:
|
||||||
|
"""Add a router to the container.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Unique name for the router
|
||||||
|
router: APIRouter instance
|
||||||
|
metadata: Optional metadata for the event
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If router name already exists
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> container = RouterContainer()
|
||||||
|
>>> router = APIRouter()
|
||||||
|
>>> container.add_router("users", router)
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating Documentation
|
||||||
|
|
||||||
|
- Update README.md for user-facing changes
|
||||||
|
- Update docstrings for API changes
|
||||||
|
- Add examples for new features
|
||||||
|
- Update CHANGELOG.md
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
1. Update version in `pyproject.toml`
|
||||||
|
2. Update CHANGELOG.md
|
||||||
|
3. Create a git tag: `git tag v0.1.0`
|
||||||
|
4. Push tag: `git push origin v0.1.0`
|
||||||
|
5. Create GitHub release
|
||||||
|
6. Publish to PyPI (maintainers only)
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
Feel free to open an issue for:
|
||||||
|
- Bug reports
|
||||||
|
- Feature requests
|
||||||
|
- Questions about contributing
|
||||||
|
- General discussion
|
||||||
|
|
||||||
|
Thank you for contributing! 🎉
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Zane Walker
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
359
README.md
Normal file
359
README.md
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
# FastAPI Route Loader
|
||||||
|
|
||||||
|
[](https://www.python.org/downloads/)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
Automatic APIRouter loading and management for FastAPI with an event dispatch system.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🚀 **Automatic Router Discovery**: Automatically load APIRouter instances from modules and directories
|
||||||
|
- 🎯 **Router Management**: Include/exclude routers dynamically with filtering capabilities
|
||||||
|
- 📡 **Event System**: Subscribe to router lifecycle events (loaded, unloaded, updated)
|
||||||
|
- 🔧 **Type Safe**: Full type hints with strict pyright and ruff compliance
|
||||||
|
- ✅ **Production Ready**: Comprehensive test coverage and battle-tested code
|
||||||
|
- 🐍 **Python 3.10+**: Modern Python with full async support
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install fastapi-route-loader
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi import FastAPI, APIRouter
|
||||||
|
from fastapi_route_loader import RouterContainer
|
||||||
|
|
||||||
|
# Create a container
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
# Add routers manually
|
||||||
|
users_router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
@users_router.get("/")
|
||||||
|
def list_users():
|
||||||
|
return {"users": []}
|
||||||
|
|
||||||
|
container.add_router("users", users_router)
|
||||||
|
|
||||||
|
# Register all routers to your FastAPI app
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Loading from Directory
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi_route_loader import RouterContainer
|
||||||
|
|
||||||
|
# Create a container and load all routers from a directory
|
||||||
|
container = RouterContainer()
|
||||||
|
container.load_from_directory("./routers", package="myapp.routers")
|
||||||
|
|
||||||
|
# Register to FastAPI app
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app, prefix="/api")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Router Filtering
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi_route_loader import RouterContainer
|
||||||
|
|
||||||
|
container = RouterContainer()
|
||||||
|
container.load_from_directory("./routers")
|
||||||
|
|
||||||
|
# Exclude specific routers
|
||||||
|
container.exclude("admin_router", "internal_router")
|
||||||
|
|
||||||
|
# Or use whitelist mode (only include specific routers)
|
||||||
|
container.include("public_router", "api_router")
|
||||||
|
|
||||||
|
# Clear all filters
|
||||||
|
container.clear_filters()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event System
|
||||||
|
|
||||||
|
The event system supports both decorator and callback styles:
|
||||||
|
|
||||||
|
**Decorator Style (Recommended):**
|
||||||
|
```python
|
||||||
|
from fastapi_route_loader import RouterContainer, RouterEventType
|
||||||
|
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
# Subscribe to specific event types using decorators
|
||||||
|
@container.on(RouterEventType.LOADED)
|
||||||
|
def on_router_loaded(event):
|
||||||
|
print(f"Router '{event.router_name}' was loaded")
|
||||||
|
print(f"Metadata: {event.metadata}")
|
||||||
|
|
||||||
|
# Subscribe to all events
|
||||||
|
@container.on(None)
|
||||||
|
def log_all_events(event):
|
||||||
|
print(f"Event: {event.event_type.value} - Router: {event.router_name}")
|
||||||
|
|
||||||
|
# Load routers (events will be dispatched)
|
||||||
|
container.load_from_directory("./routers")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Callback Style:**
|
||||||
|
```python
|
||||||
|
def on_router_loaded(event):
|
||||||
|
print(f"Router '{event.router_name}' was loaded")
|
||||||
|
|
||||||
|
container.on(RouterEventType.LOADED, on_router_loaded)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
myapp/
|
||||||
|
├── main.py
|
||||||
|
└── routers/
|
||||||
|
├── __init__.py
|
||||||
|
├── users.py
|
||||||
|
├── posts.py
|
||||||
|
└── admin/
|
||||||
|
├── __init__.py
|
||||||
|
└── dashboard.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**routers/users.py:**
|
||||||
|
```python
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
users_router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
@users_router.get("/")
|
||||||
|
def list_users():
|
||||||
|
return {"users": []}
|
||||||
|
|
||||||
|
@users_router.get("/{user_id}")
|
||||||
|
def get_user(user_id: int):
|
||||||
|
return {"user_id": user_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**main.py:**
|
||||||
|
```python
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi_route_loader import RouterContainer
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
# Load all routers from the routers directory
|
||||||
|
container.load_from_directory("./routers", package="myapp.routers")
|
||||||
|
|
||||||
|
# Register all active routers
|
||||||
|
container.register_to_app(app)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic Router Management
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from fastapi_route_loader import RouterContainer
|
||||||
|
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
# Add a router
|
||||||
|
router_v1 = APIRouter(prefix="/api/v1")
|
||||||
|
container.add_router("api_v1", router_v1)
|
||||||
|
|
||||||
|
# Update a router
|
||||||
|
router_v2 = APIRouter(prefix="/api/v2")
|
||||||
|
container.update_router("api_v1", router_v2)
|
||||||
|
|
||||||
|
# Remove a router
|
||||||
|
container.remove_router("api_v1")
|
||||||
|
|
||||||
|
# Check if router exists
|
||||||
|
if container.has_router("api_v1"):
|
||||||
|
router = container.get_router("api_v1")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Handling with Metadata
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi_route_loader import RouterContainer, RouterEventType
|
||||||
|
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
def track_router_changes(event):
|
||||||
|
if event.event_type == RouterEventType.LOADED:
|
||||||
|
print(f"Loaded: {event.router_name}")
|
||||||
|
elif event.event_type == RouterEventType.UPDATED:
|
||||||
|
print(f"Updated: {event.router_name}")
|
||||||
|
print(f"Old router: {event.old_router}")
|
||||||
|
elif event.event_type == RouterEventType.UNLOADED:
|
||||||
|
print(f"Unloaded: {event.router_name}")
|
||||||
|
|
||||||
|
container.on(None, track_router_changes)
|
||||||
|
|
||||||
|
# Add router with metadata
|
||||||
|
from fastapi import APIRouter
|
||||||
|
router = APIRouter()
|
||||||
|
container.add_router("my_router", router, metadata={"version": "1.0", "author": "dev"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Iteration
|
||||||
|
|
||||||
|
```python
|
||||||
|
container = RouterContainer()
|
||||||
|
# ... add routers ...
|
||||||
|
|
||||||
|
# Check length
|
||||||
|
print(f"Total routers: {len(container)}")
|
||||||
|
|
||||||
|
# Check membership
|
||||||
|
if "users_router" in container:
|
||||||
|
print("Users router exists")
|
||||||
|
|
||||||
|
# Iterate over router names
|
||||||
|
for router_name in container:
|
||||||
|
print(f"Router: {router_name}")
|
||||||
|
|
||||||
|
# Get all routers (including filtered ones)
|
||||||
|
all_routers = container.get_all_routers()
|
||||||
|
|
||||||
|
# Get only active routers (after applying filters)
|
||||||
|
active_routers = container.get_active_routers()
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### RouterContainer
|
||||||
|
|
||||||
|
Main class for managing APIRouter instances.
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
- `add_router(name: str, router: APIRouter, metadata: dict | None = None) -> None`
|
||||||
|
- Add a router to the container
|
||||||
|
|
||||||
|
- `remove_router(name: str, metadata: dict | None = None) -> APIRouter`
|
||||||
|
- Remove and return a router from the container
|
||||||
|
|
||||||
|
- `update_router(name: str, router: APIRouter, metadata: dict | None = None) -> None`
|
||||||
|
- Update an existing router
|
||||||
|
|
||||||
|
- `get_router(name: str) -> APIRouter`
|
||||||
|
- Get a router by name
|
||||||
|
|
||||||
|
- `has_router(name: str) -> bool`
|
||||||
|
- Check if a router exists
|
||||||
|
|
||||||
|
- `exclude(*names: str) -> None`
|
||||||
|
- Exclude routers from being active
|
||||||
|
|
||||||
|
- `include(*names: str) -> None`
|
||||||
|
- Set routers to be included (whitelist mode)
|
||||||
|
|
||||||
|
- `clear_filters() -> None`
|
||||||
|
- Clear all include/exclude filters
|
||||||
|
|
||||||
|
- `is_active(name: str) -> bool`
|
||||||
|
- Check if a router is active
|
||||||
|
|
||||||
|
- `get_active_routers() -> dict[str, APIRouter]`
|
||||||
|
- Get all active routers
|
||||||
|
|
||||||
|
- `get_all_routers() -> dict[str, APIRouter]`
|
||||||
|
- Get all routers regardless of filters
|
||||||
|
|
||||||
|
- `load_from_module(module_path: str) -> None`
|
||||||
|
- Load routers from a module
|
||||||
|
|
||||||
|
- `load_from_directory(directory: str, package: str | None = None) -> None`
|
||||||
|
- Load routers from a directory
|
||||||
|
|
||||||
|
- `register_to_app(app: FastAPI, prefix: str = "") -> None`
|
||||||
|
- Register all active routers to a FastAPI application
|
||||||
|
|
||||||
|
- `on(event_type: RouterEventType | None, handler: EventHandler) -> None`
|
||||||
|
- Subscribe to router events
|
||||||
|
|
||||||
|
- `off(event_type: RouterEventType | None, handler: EventHandler) -> None`
|
||||||
|
- Unsubscribe from router events
|
||||||
|
|
||||||
|
- `clear_handlers() -> None`
|
||||||
|
- Clear all event handlers
|
||||||
|
|
||||||
|
### RouterEventType
|
||||||
|
|
||||||
|
Enum of event types:
|
||||||
|
- `LOADED`: Router was loaded
|
||||||
|
- `UNLOADED`: Router was unloaded
|
||||||
|
- `UPDATED`: Router was updated
|
||||||
|
|
||||||
|
### Event Classes
|
||||||
|
|
||||||
|
- `RouterLoadedEvent`: Dispatched when a router is loaded
|
||||||
|
- `RouterUnloadedEvent`: Dispatched when a router is unloaded
|
||||||
|
- `RouterUpdatedEvent`: Dispatched when a router is updated (includes `old_router` attribute)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/yourusername/fastapi-route-loader.git
|
||||||
|
cd fastapi-route-loader
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests with coverage
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run with verbose output
|
||||||
|
pytest -v
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
pytest tests/test_container.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run ruff linter
|
||||||
|
ruff check .
|
||||||
|
|
||||||
|
# Run ruff formatter
|
||||||
|
ruff format .
|
||||||
|
|
||||||
|
# Run pyright type checker
|
||||||
|
pyright
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### 0.1.0 (Initial Release)
|
||||||
|
|
||||||
|
- Automatic router discovery and loading
|
||||||
|
- Router filtering (include/exclude)
|
||||||
|
- Event dispatch system
|
||||||
|
- Full type hints and strict linting
|
||||||
|
- Comprehensive test coverage
|
||||||
228
SETUP_GUIDE.md
Normal file
228
SETUP_GUIDE.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# Setup and Verification Guide
|
||||||
|
|
||||||
|
This guide will help you set up the development environment and verify that everything is working correctly.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Python 3.10 or higher
|
||||||
|
- pip (Python package installer)
|
||||||
|
- git
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Setup Development Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to the project directory
|
||||||
|
cd /home/zanewalker/Development/Programming/PythonProjects/Libraries/fastapi-route-loader
|
||||||
|
|
||||||
|
# Run the setup script
|
||||||
|
bash scripts/setup.sh
|
||||||
|
|
||||||
|
# Or manually:
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verify Installation
|
||||||
|
|
||||||
|
After setup, verify everything is working:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Activate virtual environment (if not already activated)
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# Run verification script
|
||||||
|
bash scripts/verify.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Verification Steps
|
||||||
|
|
||||||
|
If you prefer to run checks individually:
|
||||||
|
|
||||||
|
### Run Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests with coverage
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
pytest tests/test_container.py
|
||||||
|
|
||||||
|
# Run with verbose output
|
||||||
|
pytest -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run ruff linter
|
||||||
|
ruff check src/ tests/ examples/
|
||||||
|
|
||||||
|
# Auto-fix issues
|
||||||
|
ruff check --fix src/ tests/ examples/
|
||||||
|
|
||||||
|
# Check formatting
|
||||||
|
ruff format --check src/ tests/ examples/
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
ruff format src/ tests/ examples/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Checking
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run pyright on source code
|
||||||
|
pyright src/
|
||||||
|
|
||||||
|
# Check specific file
|
||||||
|
pyright src/fastapi_route_loader/container.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
fastapi-route-loader/
|
||||||
|
├── src/
|
||||||
|
│ └── fastapi_route_loader/
|
||||||
|
│ ├── __init__.py # Package exports
|
||||||
|
│ ├── container.py # RouterContainer class
|
||||||
|
│ ├── events.py # Event system
|
||||||
|
│ ├── loader.py # Router loader
|
||||||
|
│ └── py.typed # Type hint marker
|
||||||
|
├── tests/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── test_container.py # Container tests
|
||||||
|
│ ├── test_events.py # Event system tests
|
||||||
|
│ ├── test_loader.py # Loader tests
|
||||||
|
│ └── test_integration.py # Integration tests
|
||||||
|
├── examples/
|
||||||
|
│ ├── basic_usage.py # Basic usage example
|
||||||
|
│ └── event_handling.py # Event handling example
|
||||||
|
├── scripts/
|
||||||
|
│ ├── setup.sh # Setup script
|
||||||
|
│ └── verify.sh # Verification script
|
||||||
|
├── pyproject.toml # Project configuration
|
||||||
|
├── README.md # Main documentation
|
||||||
|
├── CONTRIBUTING.md # Contribution guidelines
|
||||||
|
├── LICENSE # MIT License
|
||||||
|
└── .python-version # Python version (3.10)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Files
|
||||||
|
|
||||||
|
### pyproject.toml
|
||||||
|
|
||||||
|
The project uses:
|
||||||
|
- **Build System**: Hatchling
|
||||||
|
- **Linter**: Ruff with strict rules
|
||||||
|
- **Type Checker**: Pyright in strict mode
|
||||||
|
- **Test Framework**: pytest with coverage
|
||||||
|
- **Python Support**: 3.10+
|
||||||
|
|
||||||
|
### Ruff Configuration
|
||||||
|
|
||||||
|
Strict linting with comprehensive rule sets including:
|
||||||
|
- pycodestyle (E, W)
|
||||||
|
- pyflakes (F)
|
||||||
|
- isort (I)
|
||||||
|
- flake8-bugbear (B)
|
||||||
|
- pyupgrade (UP)
|
||||||
|
- And many more...
|
||||||
|
|
||||||
|
### Pyright Configuration
|
||||||
|
|
||||||
|
Strict type checking with:
|
||||||
|
- Type checking mode: strict
|
||||||
|
- Python version: 3.10
|
||||||
|
- Some unknowns allowed for external dependencies
|
||||||
|
|
||||||
|
## Running Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Activate virtual environment
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# Run basic usage example
|
||||||
|
python examples/basic_usage.py
|
||||||
|
|
||||||
|
# Run event handling example
|
||||||
|
python examples/event_handling.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Virtual Environment Issues
|
||||||
|
|
||||||
|
If you encounter issues with the virtual environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove existing venv
|
||||||
|
rm -rf .venv
|
||||||
|
|
||||||
|
# Create new venv
|
||||||
|
python3 -m venv .venv
|
||||||
|
|
||||||
|
# Activate and reinstall
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import Errors
|
||||||
|
|
||||||
|
If you get import errors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure package is installed in editable mode
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
# Or with dev dependencies
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Failures
|
||||||
|
|
||||||
|
If tests fail:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests with verbose output
|
||||||
|
pytest -v
|
||||||
|
|
||||||
|
# Run specific failing test
|
||||||
|
pytest tests/test_container.py::TestRouterContainer::test_add_router -v
|
||||||
|
|
||||||
|
# Check for missing dependencies
|
||||||
|
pip install pytest pytest-cov pytest-asyncio httpx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Checking Errors
|
||||||
|
|
||||||
|
The library code is designed to pass strict type checking. However, you may see warnings about:
|
||||||
|
- Unknown types from FastAPI (expected when FastAPI is not installed)
|
||||||
|
- Test fixtures (tmp_path, etc.) - these are ignored in test files
|
||||||
|
|
||||||
|
To verify the source code only:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pyright src/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Read the [README.md](README.md) for usage examples
|
||||||
|
2. Check [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines
|
||||||
|
3. Explore the [examples/](examples/) directory
|
||||||
|
4. Run the tests to see the library in action
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you encounter any issues:
|
||||||
|
1. Check this guide
|
||||||
|
2. Review the README.md
|
||||||
|
3. Check existing GitHub issues
|
||||||
|
4. Create a new issue with details
|
||||||
|
|
||||||
|
Happy coding! 🚀
|
||||||
42
examples/basic_usage.py
Normal file
42
examples/basic_usage.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""Basic usage example of fastapi-route-loader."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, FastAPI
|
||||||
|
|
||||||
|
from fastapi_route_loader import RouterContainer
|
||||||
|
|
||||||
|
app = FastAPI(title="Basic Usage Example")
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
users_router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.get("/")
|
||||||
|
def list_users() -> dict[str, list[str]]:
|
||||||
|
"""List all users."""
|
||||||
|
return {"users": ["alice", "bob"]}
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.get("/{user_id}")
|
||||||
|
def get_user(user_id: int) -> dict[str, int]:
|
||||||
|
"""Get a specific user."""
|
||||||
|
return {"user_id": user_id}
|
||||||
|
|
||||||
|
|
||||||
|
posts_router = APIRouter(prefix="/posts", tags=["posts"])
|
||||||
|
|
||||||
|
|
||||||
|
@posts_router.get("/")
|
||||||
|
def list_posts() -> dict[str, list[str]]:
|
||||||
|
"""List all posts."""
|
||||||
|
return {"posts": ["post1", "post2"]}
|
||||||
|
|
||||||
|
|
||||||
|
container.add_router("users", users_router)
|
||||||
|
container.add_router("posts", posts_router)
|
||||||
|
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
71
examples/event_handling.py
Normal file
71
examples/event_handling.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"""Event handling example for fastapi-route-loader."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, FastAPI
|
||||||
|
|
||||||
|
from fastapi_route_loader import (
|
||||||
|
RouterContainer,
|
||||||
|
RouterEvent,
|
||||||
|
RouterEventType,
|
||||||
|
)
|
||||||
|
|
||||||
|
app = FastAPI(title="Event Handling Example")
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
event_log: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
@container.on(None)
|
||||||
|
def log_router_event(event: RouterEvent) -> None:
|
||||||
|
"""Log all router events."""
|
||||||
|
message = f"[{event.event_type.value.upper()}] Router: {event.router_name}"
|
||||||
|
event_log.append(message)
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
@container.on(RouterEventType.LOADED)
|
||||||
|
def on_router_loaded(event: RouterEvent) -> None:
|
||||||
|
"""Handle router loaded events."""
|
||||||
|
print(f"✅ Router '{event.router_name}' has been loaded")
|
||||||
|
if event.metadata:
|
||||||
|
print(f" Metadata: {event.metadata}")
|
||||||
|
|
||||||
|
|
||||||
|
@container.on(RouterEventType.UNLOADED)
|
||||||
|
def on_router_unloaded(event: RouterEvent) -> None:
|
||||||
|
"""Handle router unloaded events."""
|
||||||
|
print(f"❌ Router '{event.router_name}' has been unloaded")
|
||||||
|
|
||||||
|
users_router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.get("/")
|
||||||
|
def list_users() -> dict[str, list[str]]:
|
||||||
|
"""List all users."""
|
||||||
|
return {"users": ["alice", "bob"]}
|
||||||
|
|
||||||
|
|
||||||
|
container.add_router("users", users_router, metadata={"version": "1.0"})
|
||||||
|
|
||||||
|
posts_router = APIRouter(prefix="/posts", tags=["posts"])
|
||||||
|
|
||||||
|
|
||||||
|
@posts_router.get("/")
|
||||||
|
def list_posts() -> dict[str, list[str]]:
|
||||||
|
"""List all posts."""
|
||||||
|
return {"posts": ["post1", "post2"]}
|
||||||
|
|
||||||
|
|
||||||
|
container.add_router("posts", posts_router, metadata={"version": "1.0"})
|
||||||
|
|
||||||
|
container.remove_router("posts")
|
||||||
|
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("\n=== Event Log ===")
|
||||||
|
for log_entry in event_log:
|
||||||
|
print(log_entry)
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
147
pyproject.toml
Normal file
147
pyproject.toml
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
[project]
|
||||||
|
name = "fastapi-route-loader"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Automatic APIRouter loading and management for FastAPI with event dispatch system"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
authors = [
|
||||||
|
{name = "Zane Walker"}
|
||||||
|
]
|
||||||
|
license = {text = "MIT"}
|
||||||
|
keywords = ["fastapi", "router", "loader", "api", "events"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Framework :: FastAPI",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"fastapi>=0.100.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.4.0",
|
||||||
|
"pytest-cov>=4.1.0",
|
||||||
|
"pytest-asyncio>=0.21.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
"pyright>=1.1.0",
|
||||||
|
"httpx>=0.24.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src/fastapi_route_loader"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py310"
|
||||||
|
line-length = 88
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"C", # flake8-comprehensions
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"N", # pep8-naming
|
||||||
|
"YTT", # flake8-2020
|
||||||
|
"ANN", # flake8-annotations
|
||||||
|
"S", # flake8-bandit
|
||||||
|
"BLE", # flake8-blind-except
|
||||||
|
"FBT", # flake8-boolean-trap
|
||||||
|
"A", # flake8-builtins
|
||||||
|
"COM", # flake8-commas
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"DTZ", # flake8-datetimez
|
||||||
|
"T10", # flake8-debugger
|
||||||
|
"EM", # flake8-errmsg
|
||||||
|
"ISC", # flake8-implicit-str-concat
|
||||||
|
"ICN", # flake8-import-conventions
|
||||||
|
"G", # flake8-logging-format
|
||||||
|
"INP", # flake8-no-pep420
|
||||||
|
"PIE", # flake8-pie
|
||||||
|
"T20", # flake8-print
|
||||||
|
"PT", # flake8-pytest-style
|
||||||
|
"Q", # flake8-quotes
|
||||||
|
"RSE", # flake8-raise
|
||||||
|
"RET", # flake8-return
|
||||||
|
"SLF", # flake8-self
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"TID", # flake8-tidy-imports
|
||||||
|
"ARG", # flake8-unused-arguments
|
||||||
|
"PTH", # flake8-use-pathlib
|
||||||
|
"ERA", # eradicate
|
||||||
|
"PD", # pandas-vet
|
||||||
|
"PGH", # pygrep-hooks
|
||||||
|
"PL", # pylint
|
||||||
|
"TRY", # tryceratops
|
||||||
|
"FLY", # flynt
|
||||||
|
"NPY", # numpy
|
||||||
|
"PERF",# perflint
|
||||||
|
"RUF", # ruff-specific rules
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"D203", # 1 blank line required before class docstring
|
||||||
|
"D213", # Multi-line docstring summary should start at the second line
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.per-file-ignores]
|
||||||
|
"tests/*" = ["S101", "PLR2004", "ANN201", "ANN001"]
|
||||||
|
"examples/*" = ["T201", "INP001"]
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
include = ["src"]
|
||||||
|
exclude = ["**/__pycache__"]
|
||||||
|
typeCheckingMode = "strict"
|
||||||
|
pythonVersion = "3.10"
|
||||||
|
reportMissingTypeStubs = false
|
||||||
|
reportUnknownMemberType = false
|
||||||
|
reportUnknownVariableType = false
|
||||||
|
reportUnknownArgumentType = false
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"--cov=fastapi_route_loader",
|
||||||
|
"--cov-report=term-missing",
|
||||||
|
"--cov-report=html",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["src"]
|
||||||
|
omit = ["tests/*"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"def __repr__",
|
||||||
|
"raise AssertionError",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
"@abstractmethod",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest>=9.0.2",
|
||||||
|
"pytest-cov>=4.1.0",
|
||||||
|
"pytest-asyncio>=0.21.0",
|
||||||
|
"httpx>=0.24.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
"pyright>=1.1.0",
|
||||||
|
"uvicorn>=0.40.0",
|
||||||
|
]
|
||||||
23
src/fastapi_route_loader/__init__.py
Normal file
23
src/fastapi_route_loader/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"""FastAPI Route Loader - Automatic router loading and management."""
|
||||||
|
|
||||||
|
from fastapi_route_loader.container import RouterContainer
|
||||||
|
from fastapi_route_loader.events import (
|
||||||
|
RouterEvent,
|
||||||
|
RouterEventType,
|
||||||
|
RouterLoadedEvent,
|
||||||
|
RouterUnloadedEvent,
|
||||||
|
RouterUpdatedEvent,
|
||||||
|
)
|
||||||
|
from fastapi_route_loader.loader import RouterLoader
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"RouterContainer",
|
||||||
|
"RouterLoader",
|
||||||
|
"RouterEvent",
|
||||||
|
"RouterEventType",
|
||||||
|
"RouterLoadedEvent",
|
||||||
|
"RouterUnloadedEvent",
|
||||||
|
"RouterUpdatedEvent",
|
||||||
|
]
|
||||||
285
src/fastapi_route_loader/container.py
Normal file
285
src/fastapi_route_loader/container.py
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
"""Router container for managing APIRouter instances with filtering capabilities."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, overload
|
||||||
|
|
||||||
|
from fastapi_route_loader.events import (
|
||||||
|
EventDispatcher,
|
||||||
|
RouterLoadedEvent,
|
||||||
|
RouterUnloadedEvent,
|
||||||
|
RouterUpdatedEvent,
|
||||||
|
)
|
||||||
|
from fastapi_route_loader.loader import RouterLoader
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from fastapi import APIRouter, FastAPI
|
||||||
|
|
||||||
|
from fastapi_route_loader.events import EventHandler, RouterEventType
|
||||||
|
|
||||||
|
|
||||||
|
class RouterContainer:
|
||||||
|
"""Container for managing APIRouter instances with filtering and events."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._routers: dict[str, APIRouter] = {}
|
||||||
|
self._excluded: set[str] = set()
|
||||||
|
self._included: set[str] | None = None
|
||||||
|
self._dispatcher = EventDispatcher()
|
||||||
|
|
||||||
|
def add_router(
|
||||||
|
self, name: str, router: APIRouter, metadata: dict[str, Any] | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Add a router to the container.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Unique name for the router
|
||||||
|
router: APIRouter instance
|
||||||
|
metadata: Optional metadata for the event
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If router name already exists
|
||||||
|
"""
|
||||||
|
if name in self._routers:
|
||||||
|
raise ValueError(f"Router '{name}' already exists")
|
||||||
|
|
||||||
|
self._routers[name] = router
|
||||||
|
event = RouterLoadedEvent(name, router, metadata)
|
||||||
|
self._dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
def remove_router(
|
||||||
|
self, name: str, metadata: dict[str, Any] | None = None
|
||||||
|
) -> APIRouter:
|
||||||
|
"""Remove a router from the container.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the router to remove
|
||||||
|
metadata: Optional metadata for the event
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The removed APIRouter instance
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeyError: If router does not exist
|
||||||
|
"""
|
||||||
|
if name not in self._routers:
|
||||||
|
raise KeyError(f"Router '{name}' not found")
|
||||||
|
|
||||||
|
router = self._routers.pop(name)
|
||||||
|
event = RouterUnloadedEvent(name, router, metadata)
|
||||||
|
self._dispatcher.dispatch(event)
|
||||||
|
return router
|
||||||
|
|
||||||
|
def update_router(
|
||||||
|
self, name: str, router: APIRouter, metadata: dict[str, Any] | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Update an existing router.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the router to update
|
||||||
|
router: New APIRouter instance
|
||||||
|
metadata: Optional metadata for the event
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeyError: If router does not exist
|
||||||
|
"""
|
||||||
|
if name not in self._routers:
|
||||||
|
raise KeyError(f"Router '{name}' not found")
|
||||||
|
|
||||||
|
old_router = self._routers[name]
|
||||||
|
self._routers[name] = router
|
||||||
|
event = RouterUpdatedEvent(name, router, old_router, metadata)
|
||||||
|
self._dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
def get_router(self, name: str) -> APIRouter:
|
||||||
|
"""Get a router by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the router
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The APIRouter instance
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeyError: If router does not exist
|
||||||
|
"""
|
||||||
|
if name not in self._routers:
|
||||||
|
raise KeyError(f"Router '{name}' not found")
|
||||||
|
return self._routers[name]
|
||||||
|
|
||||||
|
def has_router(self, name: str) -> bool:
|
||||||
|
"""Check if a router exists.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the router
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if router exists, False otherwise
|
||||||
|
"""
|
||||||
|
return name in self._routers
|
||||||
|
|
||||||
|
def exclude(self, *names: str) -> None:
|
||||||
|
"""Exclude routers from being active.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
names: Names of routers to exclude
|
||||||
|
"""
|
||||||
|
self._excluded.update(names)
|
||||||
|
|
||||||
|
def include(self, *names: str) -> None:
|
||||||
|
"""Set routers to be included (whitelist mode).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
names: Names of routers to include
|
||||||
|
"""
|
||||||
|
if self._included is None:
|
||||||
|
self._included = set()
|
||||||
|
self._included.update(names)
|
||||||
|
|
||||||
|
def clear_filters(self) -> None:
|
||||||
|
"""Clear all include/exclude filters."""
|
||||||
|
self._excluded.clear()
|
||||||
|
self._included = None
|
||||||
|
|
||||||
|
def is_active(self, name: str) -> bool:
|
||||||
|
"""Check if a router is active (not filtered out).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the router
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if router is active, False otherwise
|
||||||
|
"""
|
||||||
|
if self._included is not None:
|
||||||
|
return name in self._included
|
||||||
|
|
||||||
|
return name not in self._excluded
|
||||||
|
|
||||||
|
def get_active_routers(self) -> dict[str, APIRouter]:
|
||||||
|
"""Get all active routers (after applying filters).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary of active routers
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
name: router
|
||||||
|
for name, router in self._routers.items()
|
||||||
|
if self.is_active(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_all_routers(self) -> dict[str, APIRouter]:
|
||||||
|
"""Get all routers regardless of filters.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary of all routers
|
||||||
|
"""
|
||||||
|
return self._routers.copy()
|
||||||
|
|
||||||
|
def load_from_module(self, module_path: str) -> None:
|
||||||
|
"""Load routers from a module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_path: Dotted module path
|
||||||
|
"""
|
||||||
|
routers = RouterLoader.load_from_module(module_path)
|
||||||
|
for name, router in routers.items():
|
||||||
|
self.add_router(name, router)
|
||||||
|
|
||||||
|
def load_from_directory(
|
||||||
|
self, directory: str, package: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Load routers from a directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Path to directory
|
||||||
|
package: Optional package name
|
||||||
|
"""
|
||||||
|
routers = RouterLoader.load_from_directory(directory, package)
|
||||||
|
for name, router in routers.items():
|
||||||
|
self.add_router(name, router)
|
||||||
|
|
||||||
|
def register_to_app(self, app: FastAPI, prefix: str = "") -> None:
|
||||||
|
"""Register all active routers to a FastAPI application.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app: FastAPI application instance
|
||||||
|
prefix: Optional prefix for all routes
|
||||||
|
"""
|
||||||
|
for router in self.get_active_routers().values():
|
||||||
|
app.include_router(router, prefix=prefix)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def on(
|
||||||
|
self, event_type: RouterEventType | None
|
||||||
|
) -> Callable[[EventHandler], EventHandler]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def on(
|
||||||
|
self, event_type: RouterEventType | None, handler: EventHandler
|
||||||
|
) -> EventHandler: ...
|
||||||
|
|
||||||
|
def on(
|
||||||
|
self, event_type: RouterEventType | None, handler: EventHandler | None = None
|
||||||
|
) -> EventHandler | Callable[[EventHandler], EventHandler]:
|
||||||
|
"""Subscribe to router events.
|
||||||
|
|
||||||
|
Can be used as a method call or as a decorator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_type: Type of event to subscribe to (None for all events)
|
||||||
|
handler: Event handler function (optional when used as decorator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The handler function (for decorator usage)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
As a method call:
|
||||||
|
>>> container.on(RouterEventType.LOADED, my_handler)
|
||||||
|
|
||||||
|
As a decorator:
|
||||||
|
>>> @container.on(RouterEventType.LOADED)
|
||||||
|
... def my_handler(event):
|
||||||
|
... pass
|
||||||
|
|
||||||
|
As a decorator for all events:
|
||||||
|
>>> @container.on(None)
|
||||||
|
... def my_handler(event):
|
||||||
|
... pass
|
||||||
|
"""
|
||||||
|
if handler is None:
|
||||||
|
# Decorator usage
|
||||||
|
def decorator(func: EventHandler) -> EventHandler:
|
||||||
|
self._dispatcher.subscribe(event_type, func)
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
else:
|
||||||
|
# Direct call usage
|
||||||
|
self._dispatcher.subscribe(event_type, handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def off(
|
||||||
|
self, event_type: RouterEventType | None, handler: EventHandler
|
||||||
|
) -> None:
|
||||||
|
"""Unsubscribe from router events.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_type: Type of event to unsubscribe from (None for all events)
|
||||||
|
handler: Event handler function
|
||||||
|
"""
|
||||||
|
self._dispatcher.unsubscribe(event_type, handler)
|
||||||
|
|
||||||
|
def clear_handlers(self) -> None:
|
||||||
|
"""Clear all event handlers."""
|
||||||
|
self._dispatcher.clear()
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
"""Return the number of routers in the container."""
|
||||||
|
return len(self._routers)
|
||||||
|
|
||||||
|
def __contains__(self, name: str) -> bool:
|
||||||
|
"""Check if a router exists in the container."""
|
||||||
|
return name in self._routers
|
||||||
|
|
||||||
|
def __iter__(self) -> Any:
|
||||||
|
"""Iterate over router names."""
|
||||||
|
return iter(self._routers)
|
||||||
122
src/fastapi_route_loader/events.py
Normal file
122
src/fastapi_route_loader/events.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"""Event system for router lifecycle management."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
||||||
|
class RouterEventType(str, Enum):
|
||||||
|
"""Types of router events."""
|
||||||
|
|
||||||
|
LOADED = "loaded"
|
||||||
|
UNLOADED = "unloaded"
|
||||||
|
UPDATED = "updated"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RouterEvent:
|
||||||
|
"""Base class for router events."""
|
||||||
|
|
||||||
|
event_type: RouterEventType
|
||||||
|
router_name: str
|
||||||
|
router: APIRouter
|
||||||
|
metadata: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RouterLoadedEvent(RouterEvent):
|
||||||
|
"""Event dispatched when a router is loaded."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, router_name: str, router: APIRouter, metadata: dict[str, Any] | None = None
|
||||||
|
) -> None:
|
||||||
|
object.__setattr__(self, "event_type", RouterEventType.LOADED)
|
||||||
|
object.__setattr__(self, "router_name", router_name)
|
||||||
|
object.__setattr__(self, "router", router)
|
||||||
|
object.__setattr__(self, "metadata", metadata or {})
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RouterUnloadedEvent(RouterEvent):
|
||||||
|
"""Event dispatched when a router is unloaded."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, router_name: str, router: APIRouter, metadata: dict[str, Any] | None = None
|
||||||
|
) -> None:
|
||||||
|
object.__setattr__(self, "event_type", RouterEventType.UNLOADED)
|
||||||
|
object.__setattr__(self, "router_name", router_name)
|
||||||
|
object.__setattr__(self, "router", router)
|
||||||
|
object.__setattr__(self, "metadata", metadata or {})
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RouterUpdatedEvent(RouterEvent):
|
||||||
|
"""Event dispatched when a router is updated."""
|
||||||
|
|
||||||
|
old_router: APIRouter
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
router_name: str,
|
||||||
|
router: APIRouter,
|
||||||
|
old_router: APIRouter,
|
||||||
|
metadata: dict[str, Any] | None = None,
|
||||||
|
) -> None:
|
||||||
|
object.__setattr__(self, "event_type", RouterEventType.UPDATED)
|
||||||
|
object.__setattr__(self, "router_name", router_name)
|
||||||
|
object.__setattr__(self, "router", router)
|
||||||
|
object.__setattr__(self, "old_router", old_router)
|
||||||
|
object.__setattr__(self, "metadata", metadata or {})
|
||||||
|
|
||||||
|
|
||||||
|
EventHandler = Callable[[RouterEvent], None]
|
||||||
|
|
||||||
|
|
||||||
|
class EventDispatcher:
|
||||||
|
"""Manages event handlers and dispatches events."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._handlers: dict[RouterEventType, list[EventHandler]] = {
|
||||||
|
RouterEventType.LOADED: [],
|
||||||
|
RouterEventType.UNLOADED: [],
|
||||||
|
RouterEventType.UPDATED: [],
|
||||||
|
}
|
||||||
|
self._global_handlers: list[EventHandler] = []
|
||||||
|
|
||||||
|
def subscribe(
|
||||||
|
self, event_type: RouterEventType | None, handler: EventHandler
|
||||||
|
) -> None:
|
||||||
|
"""Subscribe a handler to specific event type or all events."""
|
||||||
|
if event_type is None:
|
||||||
|
self._global_handlers.append(handler)
|
||||||
|
else:
|
||||||
|
self._handlers[event_type].append(handler)
|
||||||
|
|
||||||
|
def unsubscribe(
|
||||||
|
self, event_type: RouterEventType | None, handler: EventHandler
|
||||||
|
) -> None:
|
||||||
|
"""Unsubscribe a handler from specific event type or all events."""
|
||||||
|
if event_type is None:
|
||||||
|
if handler in self._global_handlers:
|
||||||
|
self._global_handlers.remove(handler)
|
||||||
|
else:
|
||||||
|
if handler in self._handlers[event_type]:
|
||||||
|
self._handlers[event_type].remove(handler)
|
||||||
|
|
||||||
|
def dispatch(self, event: RouterEvent) -> None:
|
||||||
|
"""Dispatch an event to all subscribed handlers."""
|
||||||
|
for handler in self._handlers[event.event_type]:
|
||||||
|
handler(event)
|
||||||
|
for handler in self._global_handlers:
|
||||||
|
handler(event)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clear all event handlers."""
|
||||||
|
for handlers in self._handlers.values():
|
||||||
|
handlers.clear()
|
||||||
|
self._global_handlers.clear()
|
||||||
99
src/fastapi_route_loader/loader.py
Normal file
99
src/fastapi_route_loader/loader.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
"""Router loader for automatic discovery and loading of APIRouters."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
||||||
|
class RouterLoader:
|
||||||
|
"""Loads APIRouter instances from Python modules."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load_from_module(module_path: str) -> dict[str, APIRouter]:
|
||||||
|
"""Load all APIRouter instances from a module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_path: Dotted module path (e.g., 'myapp.routers.users')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping router names to APIRouter instances
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError: If module cannot be imported
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(module_path)
|
||||||
|
except ImportError as e:
|
||||||
|
raise ImportError(f"Failed to import module '{module_path}': {e}") from e
|
||||||
|
|
||||||
|
routers: dict[str, APIRouter] = {}
|
||||||
|
|
||||||
|
for name, obj in inspect.getmembers(module):
|
||||||
|
if RouterLoader._is_router(obj):
|
||||||
|
routers[name] = obj
|
||||||
|
|
||||||
|
return routers
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load_from_directory(
|
||||||
|
directory: Path | str, package: str | None = None
|
||||||
|
) -> dict[str, APIRouter]:
|
||||||
|
"""Load all APIRouter instances from Python files in a directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Path to directory containing router modules
|
||||||
|
package: Optional package name for relative imports
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping router names to APIRouter instances
|
||||||
|
"""
|
||||||
|
directory_path = Path(directory)
|
||||||
|
if not directory_path.exists():
|
||||||
|
raise FileNotFoundError(f"Directory not found: {directory}")
|
||||||
|
if not directory_path.is_dir():
|
||||||
|
raise NotADirectoryError(f"Not a directory: {directory}")
|
||||||
|
|
||||||
|
routers: dict[str, APIRouter] = {}
|
||||||
|
|
||||||
|
for py_file in directory_path.rglob("*.py"):
|
||||||
|
if py_file.name.startswith("_"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
module_path = RouterLoader._get_module_path(py_file, directory_path, package)
|
||||||
|
try:
|
||||||
|
module_routers = RouterLoader.load_from_module(module_path)
|
||||||
|
for name, router in module_routers.items():
|
||||||
|
full_name = f"{py_file.stem}.{name}"
|
||||||
|
routers[full_name] = router
|
||||||
|
except ImportError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return routers
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_router(obj: Any) -> bool:
|
||||||
|
"""Check if an object is an APIRouter instance."""
|
||||||
|
try:
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
return isinstance(obj, APIRouter)
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_module_path(
|
||||||
|
file_path: Path, base_path: Path, package: str | None
|
||||||
|
) -> str:
|
||||||
|
"""Convert file path to module path."""
|
||||||
|
relative = file_path.relative_to(base_path)
|
||||||
|
parts = list(relative.parts[:-1]) + [relative.stem]
|
||||||
|
|
||||||
|
if package:
|
||||||
|
return f"{package}.{'.'.join(parts)}"
|
||||||
|
return ".".join(parts)
|
||||||
0
src/fastapi_route_loader/py.typed
Normal file
0
src/fastapi_route_loader/py.typed
Normal file
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
351
tests/test_container.py
Normal file
351
tests/test_container.py
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
"""Tests for router container."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import APIRouter, FastAPI
|
||||||
|
|
||||||
|
from fastapi_route_loader.container import RouterContainer
|
||||||
|
from fastapi_route_loader.events import (
|
||||||
|
RouterEventType,
|
||||||
|
RouterLoadedEvent,
|
||||||
|
RouterUnloadedEvent,
|
||||||
|
RouterUpdatedEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRouterContainer:
|
||||||
|
"""Test router container functionality."""
|
||||||
|
|
||||||
|
def test_add_router(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
|
||||||
|
assert container.has_router("test_router")
|
||||||
|
assert container.get_router("test_router") is router
|
||||||
|
assert len(container) == 1
|
||||||
|
|
||||||
|
def test_add_router_duplicate_raises_error(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Router 'test_router' already exists"):
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
|
||||||
|
def test_add_router_dispatches_event(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
container.on(RouterEventType.LOADED, handler)
|
||||||
|
container.add_router("test_router", router, {"key": "value"})
|
||||||
|
|
||||||
|
assert len(events_received) == 1
|
||||||
|
event = events_received[0]
|
||||||
|
assert isinstance(event, RouterLoadedEvent)
|
||||||
|
assert event.router_name == "test_router"
|
||||||
|
assert event.router is router
|
||||||
|
assert event.metadata == {"key": "value"}
|
||||||
|
|
||||||
|
def test_remove_router(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
removed_router = container.remove_router("test_router")
|
||||||
|
|
||||||
|
assert removed_router is router
|
||||||
|
assert not container.has_router("test_router")
|
||||||
|
assert len(container) == 0
|
||||||
|
|
||||||
|
def test_remove_router_not_found_raises_error(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
with pytest.raises(KeyError, match="Router 'nonexistent' not found"):
|
||||||
|
container.remove_router("nonexistent")
|
||||||
|
|
||||||
|
def test_remove_router_dispatches_event(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
container.on(RouterEventType.UNLOADED, handler)
|
||||||
|
container.remove_router("test_router", {"reason": "cleanup"})
|
||||||
|
|
||||||
|
assert len(events_received) == 1
|
||||||
|
event = events_received[0]
|
||||||
|
assert isinstance(event, RouterUnloadedEvent)
|
||||||
|
assert event.router_name == "test_router"
|
||||||
|
assert event.router is router
|
||||||
|
assert event.metadata == {"reason": "cleanup"}
|
||||||
|
|
||||||
|
def test_update_router(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
old_router = APIRouter()
|
||||||
|
new_router = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("test_router", old_router)
|
||||||
|
container.update_router("test_router", new_router)
|
||||||
|
|
||||||
|
assert container.get_router("test_router") is new_router
|
||||||
|
|
||||||
|
def test_update_router_not_found_raises_error(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
with pytest.raises(KeyError, match="Router 'nonexistent' not found"):
|
||||||
|
container.update_router("nonexistent", router)
|
||||||
|
|
||||||
|
def test_update_router_dispatches_event(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
old_router = APIRouter()
|
||||||
|
new_router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
container.add_router("test_router", old_router)
|
||||||
|
container.on(RouterEventType.UPDATED, handler)
|
||||||
|
container.update_router("test_router", new_router, {"version": "2.0"})
|
||||||
|
|
||||||
|
assert len(events_received) == 1
|
||||||
|
event = events_received[0]
|
||||||
|
assert isinstance(event, RouterUpdatedEvent)
|
||||||
|
assert event.router_name == "test_router"
|
||||||
|
assert event.router is new_router
|
||||||
|
assert event.old_router is old_router
|
||||||
|
assert event.metadata == {"version": "2.0"}
|
||||||
|
|
||||||
|
def test_get_router_not_found_raises_error(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
with pytest.raises(KeyError, match="Router 'nonexistent' not found"):
|
||||||
|
container.get_router("nonexistent")
|
||||||
|
|
||||||
|
def test_exclude_routers(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router1 = APIRouter()
|
||||||
|
router2 = APIRouter()
|
||||||
|
router3 = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("router1", router1)
|
||||||
|
container.add_router("router2", router2)
|
||||||
|
container.add_router("router3", router3)
|
||||||
|
|
||||||
|
container.exclude("router2")
|
||||||
|
|
||||||
|
assert container.is_active("router1")
|
||||||
|
assert not container.is_active("router2")
|
||||||
|
assert container.is_active("router3")
|
||||||
|
|
||||||
|
active_routers = container.get_active_routers()
|
||||||
|
assert "router1" in active_routers
|
||||||
|
assert "router2" not in active_routers
|
||||||
|
assert "router3" in active_routers
|
||||||
|
|
||||||
|
def test_include_routers(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router1 = APIRouter()
|
||||||
|
router2 = APIRouter()
|
||||||
|
router3 = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("router1", router1)
|
||||||
|
container.add_router("router2", router2)
|
||||||
|
container.add_router("router3", router3)
|
||||||
|
|
||||||
|
container.include("router1", "router3")
|
||||||
|
|
||||||
|
assert container.is_active("router1")
|
||||||
|
assert not container.is_active("router2")
|
||||||
|
assert container.is_active("router3")
|
||||||
|
|
||||||
|
active_routers = container.get_active_routers()
|
||||||
|
assert "router1" in active_routers
|
||||||
|
assert "router2" not in active_routers
|
||||||
|
assert "router3" in active_routers
|
||||||
|
|
||||||
|
def test_clear_filters(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router1 = APIRouter()
|
||||||
|
router2 = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("router1", router1)
|
||||||
|
container.add_router("router2", router2)
|
||||||
|
|
||||||
|
container.exclude("router1")
|
||||||
|
assert not container.is_active("router1")
|
||||||
|
|
||||||
|
container.clear_filters()
|
||||||
|
assert container.is_active("router1")
|
||||||
|
assert container.is_active("router2")
|
||||||
|
|
||||||
|
def test_get_all_routers(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router1 = APIRouter()
|
||||||
|
router2 = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("router1", router1)
|
||||||
|
container.add_router("router2", router2)
|
||||||
|
container.exclude("router1")
|
||||||
|
|
||||||
|
all_routers = container.get_all_routers()
|
||||||
|
assert len(all_routers) == 2
|
||||||
|
assert "router1" in all_routers
|
||||||
|
assert "router2" in all_routers
|
||||||
|
|
||||||
|
def test_load_from_module(self, tmp_path):
|
||||||
|
module_content = '''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
test_router = APIRouter()
|
||||||
|
'''
|
||||||
|
module_file = tmp_path / "test_module.py"
|
||||||
|
module_file.write_text(module_content)
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
container = RouterContainer()
|
||||||
|
container.load_from_module("test_module")
|
||||||
|
|
||||||
|
assert container.has_router("test_router")
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
if "test_module" in sys.modules:
|
||||||
|
del sys.modules["test_module"]
|
||||||
|
|
||||||
|
def test_load_from_directory(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(routers_dir / "users.py").write_text(
|
||||||
|
"from fastapi import APIRouter\nuser_router = APIRouter()"
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
container = RouterContainer()
|
||||||
|
container.load_from_directory(str(routers_dir), "routers")
|
||||||
|
|
||||||
|
assert container.has_router("users.user_router")
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_register_to_app(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router1 = APIRouter()
|
||||||
|
router2 = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("router1", router1)
|
||||||
|
container.add_router("router2", router2)
|
||||||
|
container.exclude("router2")
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
assert len(app.routes) > 0
|
||||||
|
|
||||||
|
def test_register_to_app_with_prefix(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
router.get("/")(lambda: {"message": "test"})
|
||||||
|
|
||||||
|
container.add_router("router", router)
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app, prefix="/api")
|
||||||
|
|
||||||
|
route_paths = [route.path for route in app.routes]
|
||||||
|
assert any("/api" in path for path in route_paths)
|
||||||
|
|
||||||
|
def test_event_subscription(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
container.on(None, handler)
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
container.remove_router("test_router")
|
||||||
|
|
||||||
|
assert len(events_received) == 2
|
||||||
|
|
||||||
|
def test_event_unsubscription(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
container.on(RouterEventType.LOADED, handler)
|
||||||
|
container.off(RouterEventType.LOADED, handler)
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
|
||||||
|
assert len(events_received) == 0
|
||||||
|
|
||||||
|
def test_clear_handlers(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
container.on(RouterEventType.LOADED, handler)
|
||||||
|
container.clear_handlers()
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
|
||||||
|
assert len(events_received) == 0
|
||||||
|
|
||||||
|
def test_container_len(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
assert len(container) == 0
|
||||||
|
|
||||||
|
container.add_router("router1", APIRouter())
|
||||||
|
assert len(container) == 1
|
||||||
|
|
||||||
|
container.add_router("router2", APIRouter())
|
||||||
|
assert len(container) == 2
|
||||||
|
|
||||||
|
container.remove_router("router1")
|
||||||
|
assert len(container) == 1
|
||||||
|
|
||||||
|
def test_container_contains(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("test_router", router)
|
||||||
|
|
||||||
|
assert "test_router" in container
|
||||||
|
assert "nonexistent" not in container
|
||||||
|
|
||||||
|
def test_container_iter(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
container.add_router("router1", APIRouter())
|
||||||
|
container.add_router("router2", APIRouter())
|
||||||
|
container.add_router("router3", APIRouter())
|
||||||
|
|
||||||
|
router_names = list(container)
|
||||||
|
assert "router1" in router_names
|
||||||
|
assert "router2" in router_names
|
||||||
|
assert "router3" in router_names
|
||||||
|
assert len(router_names) == 3
|
||||||
190
tests/test_events.py
Normal file
190
tests/test_events.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
"""Tests for event system."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from fastapi_route_loader.events import (
|
||||||
|
EventDispatcher,
|
||||||
|
RouterEvent,
|
||||||
|
RouterEventType,
|
||||||
|
RouterLoadedEvent,
|
||||||
|
RouterUnloadedEvent,
|
||||||
|
RouterUpdatedEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRouterEvents:
|
||||||
|
"""Test router event classes."""
|
||||||
|
|
||||||
|
def test_router_loaded_event(self):
|
||||||
|
router = APIRouter()
|
||||||
|
event = RouterLoadedEvent("test_router", router, {"key": "value"})
|
||||||
|
|
||||||
|
assert event.event_type == RouterEventType.LOADED
|
||||||
|
assert event.router_name == "test_router"
|
||||||
|
assert event.router is router
|
||||||
|
assert event.metadata == {"key": "value"}
|
||||||
|
|
||||||
|
def test_router_loaded_event_no_metadata(self):
|
||||||
|
router = APIRouter()
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
|
||||||
|
assert event.event_type == RouterEventType.LOADED
|
||||||
|
assert event.metadata == {}
|
||||||
|
|
||||||
|
def test_router_unloaded_event(self):
|
||||||
|
router = APIRouter()
|
||||||
|
event = RouterUnloadedEvent("test_router", router, {"reason": "cleanup"})
|
||||||
|
|
||||||
|
assert event.event_type == RouterEventType.UNLOADED
|
||||||
|
assert event.router_name == "test_router"
|
||||||
|
assert event.router is router
|
||||||
|
assert event.metadata == {"reason": "cleanup"}
|
||||||
|
|
||||||
|
def test_router_updated_event(self):
|
||||||
|
old_router = APIRouter()
|
||||||
|
new_router = APIRouter()
|
||||||
|
event = RouterUpdatedEvent(
|
||||||
|
"test_router", new_router, old_router, {"version": "2.0"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert event.event_type == RouterEventType.UPDATED
|
||||||
|
assert event.router_name == "test_router"
|
||||||
|
assert event.router is new_router
|
||||||
|
assert event.old_router is old_router
|
||||||
|
assert event.metadata == {"version": "2.0"}
|
||||||
|
|
||||||
|
def test_event_immutability(self):
|
||||||
|
router = APIRouter()
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
event.router_name = "new_name"
|
||||||
|
|
||||||
|
|
||||||
|
class TestEventDispatcher:
|
||||||
|
"""Test event dispatcher."""
|
||||||
|
|
||||||
|
def test_subscribe_and_dispatch(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event: RouterEvent) -> None:
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler)
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
assert len(events_received) == 1
|
||||||
|
assert events_received[0] is event
|
||||||
|
|
||||||
|
def test_subscribe_to_all_events(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event: RouterEvent) -> None:
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(None, handler)
|
||||||
|
|
||||||
|
loaded_event = RouterLoadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(loaded_event)
|
||||||
|
|
||||||
|
unloaded_event = RouterUnloadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(unloaded_event)
|
||||||
|
|
||||||
|
assert len(events_received) == 2
|
||||||
|
assert events_received[0] is loaded_event
|
||||||
|
assert events_received[1] is unloaded_event
|
||||||
|
|
||||||
|
def test_multiple_handlers(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
handler1_calls = []
|
||||||
|
handler2_calls = []
|
||||||
|
|
||||||
|
def handler1(event: RouterEvent) -> None:
|
||||||
|
handler1_calls.append(event)
|
||||||
|
|
||||||
|
def handler2(event: RouterEvent) -> None:
|
||||||
|
handler2_calls.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler1)
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler2)
|
||||||
|
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
assert len(handler1_calls) == 1
|
||||||
|
assert len(handler2_calls) == 1
|
||||||
|
|
||||||
|
def test_unsubscribe(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event: RouterEvent) -> None:
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler)
|
||||||
|
dispatcher.unsubscribe(RouterEventType.LOADED, handler)
|
||||||
|
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
assert len(events_received) == 0
|
||||||
|
|
||||||
|
def test_unsubscribe_global_handler(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event: RouterEvent) -> None:
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(None, handler)
|
||||||
|
dispatcher.unsubscribe(None, handler)
|
||||||
|
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
assert len(events_received) == 0
|
||||||
|
|
||||||
|
def test_clear_handlers(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
events_received = []
|
||||||
|
|
||||||
|
def handler(event: RouterEvent) -> None:
|
||||||
|
events_received.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler)
|
||||||
|
dispatcher.subscribe(None, handler)
|
||||||
|
dispatcher.clear()
|
||||||
|
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
dispatcher.dispatch(event)
|
||||||
|
|
||||||
|
assert len(events_received) == 0
|
||||||
|
|
||||||
|
def test_handler_exception_does_not_stop_other_handlers(self):
|
||||||
|
dispatcher = EventDispatcher()
|
||||||
|
router = APIRouter()
|
||||||
|
handler2_calls = []
|
||||||
|
|
||||||
|
def handler1(event: RouterEvent) -> None:
|
||||||
|
raise ValueError("Handler error")
|
||||||
|
|
||||||
|
def handler2(event: RouterEvent) -> None:
|
||||||
|
handler2_calls.append(event)
|
||||||
|
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler1)
|
||||||
|
dispatcher.subscribe(RouterEventType.LOADED, handler2)
|
||||||
|
|
||||||
|
event = RouterLoadedEvent("test_router", router)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Handler error"):
|
||||||
|
dispatcher.dispatch(event)
|
||||||
261
tests/test_integration.py
Normal file
261
tests/test_integration.py
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
"""Integration tests for the complete library."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from fastapi import APIRouter, FastAPI
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from fastapi_route_loader import RouterContainer, RouterEventType
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntegration:
|
||||||
|
"""Test complete integration scenarios."""
|
||||||
|
|
||||||
|
def test_full_workflow(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(routers_dir / "users.py").write_text('''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
users_router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
@users_router.get("/")
|
||||||
|
def list_users():
|
||||||
|
return {"users": []}
|
||||||
|
|
||||||
|
@users_router.get("/{user_id}")
|
||||||
|
def get_user(user_id: int):
|
||||||
|
return {"user_id": user_id}
|
||||||
|
''')
|
||||||
|
|
||||||
|
(routers_dir / "posts.py").write_text('''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
posts_router = APIRouter(prefix="/posts", tags=["posts"])
|
||||||
|
|
||||||
|
@posts_router.get("/")
|
||||||
|
def list_posts():
|
||||||
|
return {"posts": []}
|
||||||
|
''')
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
container = RouterContainer()
|
||||||
|
container.load_from_directory(str(routers_dir), "routers")
|
||||||
|
|
||||||
|
assert container.has_router("users.users_router")
|
||||||
|
assert container.has_router("posts.posts_router")
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
response = client.get("/users/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"users": []}
|
||||||
|
|
||||||
|
response = client.get("/users/123")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"user_id": 123}
|
||||||
|
|
||||||
|
response = client.get("/posts/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"posts": []}
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_filtering_workflow(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(routers_dir / "public.py").write_text('''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
public_router = APIRouter(prefix="/public")
|
||||||
|
|
||||||
|
@public_router.get("/")
|
||||||
|
def public_endpoint():
|
||||||
|
return {"message": "public"}
|
||||||
|
''')
|
||||||
|
|
||||||
|
(routers_dir / "admin.py").write_text('''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
admin_router = APIRouter(prefix="/admin")
|
||||||
|
|
||||||
|
@admin_router.get("/")
|
||||||
|
def admin_endpoint():
|
||||||
|
return {"message": "admin"}
|
||||||
|
''')
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
container = RouterContainer()
|
||||||
|
container.load_from_directory(str(routers_dir), "routers")
|
||||||
|
|
||||||
|
container.exclude("admin.admin_router")
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
response = client.get("/public/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = client.get("/admin/")
|
||||||
|
assert response.status_code == 404
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_event_tracking_workflow(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(routers_dir / "api.py").write_text('''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
api_router = APIRouter()
|
||||||
|
''')
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
container = RouterContainer()
|
||||||
|
events_log = []
|
||||||
|
|
||||||
|
def event_logger(event):
|
||||||
|
events_log.append({
|
||||||
|
"type": event.event_type.value,
|
||||||
|
"router": event.router_name,
|
||||||
|
})
|
||||||
|
|
||||||
|
container.on(None, event_logger)
|
||||||
|
|
||||||
|
container.load_from_directory(str(routers_dir), "routers")
|
||||||
|
|
||||||
|
assert len(events_log) == 1
|
||||||
|
assert events_log[0]["type"] == "loaded"
|
||||||
|
assert events_log[0]["router"] == "api.api_router"
|
||||||
|
|
||||||
|
new_router = APIRouter()
|
||||||
|
container.update_router("api.api_router", new_router)
|
||||||
|
|
||||||
|
assert len(events_log) == 2
|
||||||
|
assert events_log[1]["type"] == "updated"
|
||||||
|
|
||||||
|
container.remove_router("api.api_router")
|
||||||
|
|
||||||
|
assert len(events_log) == 3
|
||||||
|
assert events_log[2]["type"] == "unloaded"
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_manual_router_management(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
users_router = APIRouter(prefix="/users")
|
||||||
|
|
||||||
|
@users_router.get("/")
|
||||||
|
def list_users():
|
||||||
|
return {"users": []}
|
||||||
|
|
||||||
|
posts_router = APIRouter(prefix="/posts")
|
||||||
|
|
||||||
|
@posts_router.get("/")
|
||||||
|
def list_posts():
|
||||||
|
return {"posts": []}
|
||||||
|
|
||||||
|
container.add_router("users", users_router)
|
||||||
|
container.add_router("posts", posts_router)
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
response = client.get("/users/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = client.get("/posts/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_include_only_specific_routers(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
router1 = APIRouter(prefix="/api1")
|
||||||
|
router2 = APIRouter(prefix="/api2")
|
||||||
|
router3 = APIRouter(prefix="/api3")
|
||||||
|
|
||||||
|
@router1.get("/")
|
||||||
|
def api1():
|
||||||
|
return {"api": 1}
|
||||||
|
|
||||||
|
@router2.get("/")
|
||||||
|
def api2():
|
||||||
|
return {"api": 2}
|
||||||
|
|
||||||
|
@router3.get("/")
|
||||||
|
def api3():
|
||||||
|
return {"api": 3}
|
||||||
|
|
||||||
|
container.add_router("api1", router1)
|
||||||
|
container.add_router("api2", router2)
|
||||||
|
container.add_router("api3", router3)
|
||||||
|
|
||||||
|
container.include("api1", "api3")
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
container.register_to_app(app)
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
response = client.get("/api1/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = client.get("/api2/")
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
response = client.get("/api3/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_event_handlers_for_specific_types(self):
|
||||||
|
container = RouterContainer()
|
||||||
|
|
||||||
|
loaded_count = [0]
|
||||||
|
unloaded_count = [0]
|
||||||
|
updated_count = [0]
|
||||||
|
|
||||||
|
def on_loaded(event):
|
||||||
|
loaded_count[0] += 1
|
||||||
|
|
||||||
|
def on_unloaded(event):
|
||||||
|
unloaded_count[0] += 1
|
||||||
|
|
||||||
|
def on_updated(event):
|
||||||
|
updated_count[0] += 1
|
||||||
|
|
||||||
|
container.on(RouterEventType.LOADED, on_loaded)
|
||||||
|
container.on(RouterEventType.UNLOADED, on_unloaded)
|
||||||
|
container.on(RouterEventType.UPDATED, on_updated)
|
||||||
|
|
||||||
|
router1 = APIRouter()
|
||||||
|
router2 = APIRouter()
|
||||||
|
|
||||||
|
container.add_router("router1", router1)
|
||||||
|
assert loaded_count[0] == 1
|
||||||
|
|
||||||
|
container.update_router("router1", router2)
|
||||||
|
assert updated_count[0] == 1
|
||||||
|
|
||||||
|
container.remove_router("router1")
|
||||||
|
assert unloaded_count[0] == 1
|
||||||
160
tests/test_loader.py
Normal file
160
tests/test_loader.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"""Tests for router loader."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from fastapi_route_loader.loader import RouterLoader
|
||||||
|
|
||||||
|
|
||||||
|
class TestRouterLoader:
|
||||||
|
"""Test router loader functionality."""
|
||||||
|
|
||||||
|
def test_is_router_with_api_router(self):
|
||||||
|
router = APIRouter()
|
||||||
|
assert RouterLoader._is_router(router) is True
|
||||||
|
|
||||||
|
def test_is_router_with_non_router(self):
|
||||||
|
assert RouterLoader._is_router("not a router") is False
|
||||||
|
assert RouterLoader._is_router(123) is False
|
||||||
|
assert RouterLoader._is_router(None) is False
|
||||||
|
|
||||||
|
def test_get_module_path_without_package(self):
|
||||||
|
base_path = Path("/app/routers")
|
||||||
|
file_path = Path("/app/routers/users/api.py")
|
||||||
|
module_path = RouterLoader._get_module_path(file_path, base_path, None)
|
||||||
|
assert module_path == "users.api"
|
||||||
|
|
||||||
|
def test_get_module_path_with_package(self):
|
||||||
|
base_path = Path("/app/routers")
|
||||||
|
file_path = Path("/app/routers/users/api.py")
|
||||||
|
module_path = RouterLoader._get_module_path(file_path, base_path, "myapp")
|
||||||
|
assert module_path == "myapp.users.api"
|
||||||
|
|
||||||
|
def test_get_module_path_single_file(self):
|
||||||
|
base_path = Path("/app/routers")
|
||||||
|
file_path = Path("/app/routers/api.py")
|
||||||
|
module_path = RouterLoader._get_module_path(file_path, base_path, None)
|
||||||
|
assert module_path == "api"
|
||||||
|
|
||||||
|
def test_load_from_module_success(self, tmp_path):
|
||||||
|
module_content = '''
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
other_router = APIRouter()
|
||||||
|
not_a_router = "test"
|
||||||
|
'''
|
||||||
|
module_file = tmp_path / "test_module.py"
|
||||||
|
module_file.write_text(module_content)
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
routers = RouterLoader.load_from_module("test_module")
|
||||||
|
assert "router" in routers
|
||||||
|
assert "other_router" in routers
|
||||||
|
assert "not_a_router" not in routers
|
||||||
|
assert isinstance(routers["router"], APIRouter)
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
if "test_module" in sys.modules:
|
||||||
|
del sys.modules["test_module"]
|
||||||
|
|
||||||
|
def test_load_from_module_import_error(self):
|
||||||
|
with pytest.raises(ImportError, match="Failed to import module"):
|
||||||
|
RouterLoader.load_from_module("nonexistent_module_xyz")
|
||||||
|
|
||||||
|
def test_load_from_directory_success(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(routers_dir / "users.py").write_text(
|
||||||
|
"from fastapi import APIRouter\nuser_router = APIRouter()"
|
||||||
|
)
|
||||||
|
(routers_dir / "posts.py").write_text(
|
||||||
|
"from fastapi import APIRouter\npost_router = APIRouter()"
|
||||||
|
)
|
||||||
|
(routers_dir / "_private.py").write_text(
|
||||||
|
"from fastapi import APIRouter\nprivate_router = APIRouter()"
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
routers = RouterLoader.load_from_directory(routers_dir, "routers")
|
||||||
|
assert "users.user_router" in routers
|
||||||
|
assert "posts.post_router" in routers
|
||||||
|
assert "private.private_router" not in routers
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_load_from_directory_not_found(self):
|
||||||
|
with pytest.raises(FileNotFoundError, match="Directory not found"):
|
||||||
|
RouterLoader.load_from_directory("/nonexistent/path")
|
||||||
|
|
||||||
|
def test_load_from_directory_not_a_directory(self, tmp_path):
|
||||||
|
file_path = tmp_path / "not_a_dir.txt"
|
||||||
|
file_path.write_text("test")
|
||||||
|
|
||||||
|
with pytest.raises(NotADirectoryError, match="Not a directory"):
|
||||||
|
RouterLoader.load_from_directory(file_path)
|
||||||
|
|
||||||
|
def test_load_from_directory_nested_structure(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
api_dir = routers_dir / "api"
|
||||||
|
api_dir.mkdir()
|
||||||
|
v1_dir = api_dir / "v1"
|
||||||
|
v1_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(api_dir / "__init__.py").write_text("")
|
||||||
|
(v1_dir / "__init__.py").write_text("")
|
||||||
|
(v1_dir / "users.py").write_text(
|
||||||
|
"from fastapi import APIRouter\nusers_router = APIRouter()"
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
routers = RouterLoader.load_from_directory(routers_dir, "routers")
|
||||||
|
assert "users.users_router" in routers
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_load_from_directory_with_import_errors(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
(routers_dir / "__init__.py").write_text("")
|
||||||
|
(routers_dir / "good.py").write_text(
|
||||||
|
"from fastapi import APIRouter\ngood_router = APIRouter()"
|
||||||
|
)
|
||||||
|
(routers_dir / "bad.py").write_text(
|
||||||
|
"import nonexistent_module\nfrom fastapi import APIRouter\nbad_router = APIRouter()"
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.path.insert(0, str(tmp_path))
|
||||||
|
try:
|
||||||
|
routers = RouterLoader.load_from_directory(routers_dir, "routers")
|
||||||
|
assert "good.good_router" in routers
|
||||||
|
assert "bad.bad_router" not in routers
|
||||||
|
finally:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
for mod in list(sys.modules.keys()):
|
||||||
|
if mod.startswith("routers"):
|
||||||
|
del sys.modules[mod]
|
||||||
|
|
||||||
|
def test_load_from_directory_empty(self, tmp_path):
|
||||||
|
routers_dir = tmp_path / "empty_routers"
|
||||||
|
routers_dir.mkdir()
|
||||||
|
|
||||||
|
routers = RouterLoader.load_from_directory(routers_dir)
|
||||||
|
assert routers == {}
|
||||||
663
uv.lock
generated
Normal file
663
uv.lock
generated
Normal file
@@ -0,0 +1,663 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-doc"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.12.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backports-asyncio-runner"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2026.1.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.13.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
toml = [
|
||||||
|
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi"
|
||||||
|
version = "0.128.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-doc" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "starlette" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi-route-loader"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "fastapi" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "pyright" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-asyncio" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "pyright" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-asyncio" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "uvicorn" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "fastapi", specifier = ">=0.100.0" },
|
||||||
|
{ name = "httpx", marker = "extra == 'dev'", specifier = ">=0.24.0" },
|
||||||
|
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.0" },
|
||||||
|
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
|
||||||
|
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" },
|
||||||
|
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" },
|
||||||
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" },
|
||||||
|
]
|
||||||
|
provides-extras = ["dev"]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "httpx", specifier = ">=0.24.0" },
|
||||||
|
{ name = "pyright", specifier = ">=1.1.0" },
|
||||||
|
{ name = "pytest", specifier = ">=9.0.2" },
|
||||||
|
{ name = "pytest-asyncio", specifier = ">=0.21.0" },
|
||||||
|
{ name = "pytest-cov", specifier = ">=4.1.0" },
|
||||||
|
{ name = "ruff", specifier = ">=0.1.0" },
|
||||||
|
{ name = "uvicorn", specifier = ">=0.40.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "h11" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "httpcore" },
|
||||||
|
{ name = "idna" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.11"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nodeenv"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.12.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.41.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyright"
|
||||||
|
version = "1.1.407"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "nodeenv" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-asyncio"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "coverage", extra = ["toml"] },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.14.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "starlette"
|
||||||
|
version = "0.50.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uvicorn"
|
||||||
|
version = "0.40.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "h11" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user