From 26788aa8b46b5fe46e3d28e3cf12b864d74cbffe Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 09:57:12 -0700 Subject: [PATCH 1/9] chore: add ruff for linting and formatting - Add ruff to dev dependencies - Configure ruff in pyproject.toml to match Google style - Add ruff linting to CI alongside pylint - Replace yapf with ruff format in CI - Remove yapf from dependencies - Add .git-blame-ignore-revs for formatting commit --- .git-blame-ignore-revs | 6 ++++++ .github/workflows/ci.yaml | 8 ++++++-- pyproject.toml | 32 ++++++++++++++++++++++++++++++++ setup.py | 4 ++-- 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..8a0641d --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# .git-blame-ignore-revs +# Use this file to ignore commits in git blame that are just formatting changes +# Configure with: git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Switch to ruff formatting +# TODO: Add commit hash here after formatting commit \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e0cfac..616d024 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -52,6 +52,10 @@ jobs: run: | source venv/bin/activate python3.10 -m pylint $(git ls-files '*.py') + - name: Lint with ruff + run: | + source venv/bin/activate + python3.10 -m ruff check . - name: Lint with mypy run: | source venv/bin/activate @@ -96,7 +100,7 @@ jobs: source venv/bin/activate pip3 install --upgrade pip python3.10 -m pip install -e ".[dev]" - - name: Check Formatting + - name: Check Formatting with ruff run: | source venv/bin/activate - yapf -d -r -p . + python3.10 -m ruff format --check . diff --git a/pyproject.toml b/pyproject.toml index f2171b2..29fa6a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,3 +21,35 @@ ignore_patterns = [ "build", "dist", ] + +[tool.ruff] +target-version = "py310" +line-length = 100 +indent-width = 4 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "PL", # pylint +] +ignore = [ + "PLR0913", # Too many arguments + "PLR0912", # Too many branches + "PLR0915", # Too many statements + "PLR2004", # Magic value used in comparison + "PLW0603", # Using the global statement + "PLC0415", # Import outside toplevel + "E501", # Line too long (handled by formatter) +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" diff --git a/setup.py b/setup.py index efbc8e0..bc0659c 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,8 @@ dev_requires = [ 'pytest>=7.1.2', 'setuptools>=63.4.2', 'pylint>=2.16.1', 'pytest-cov>=3.0.0', 'mypy>=1.0.0', 'sphinx>=6.1.3', - 'sphinxcontrib-napoleon>=0.7', 'yapf>=0.32.0', 'toml>=0.10.2', - 'google-cloud-tasks>=2.13.1' + 'sphinxcontrib-napoleon>=0.7', 'toml>=0.10.2', + 'google-cloud-tasks>=2.13.1', 'ruff>=0.1.0' ] # Read in the package metadata per recommendations from: From fba8bdadbd887d290acad66ac49466267fac6d27 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:01:25 -0700 Subject: [PATCH 2/9] style: apply ruff formatting Apply ruff formatting to entire codebase. This is a one-time formatting change to adopt modern Python formatting standards. Main changes: - Consistent double quotes for strings - Trailing commas in multi-line function parameters - Standardized spacing and line breaks - Removed trailing whitespace No functional changes - formatting only. --- async.md | 248 +++ docs/theme/devsite_translator/html.py | 71 +- example/functions/main.py | 8 +- modernization.md | 160 ++ samples/basic_alerts/functions/main.py | 36 +- samples/basic_db/functions/main.py | 1 + samples/basic_eventarc/functions/main.py | 4 +- samples/basic_firestore/functions/main.py | 7 +- samples/basic_params/functions/main.py | 32 +- samples/basic_tasks/functions/main.py | 10 +- samples/basic_test_lab/functions/main.py | 9 +- samples/identity/functions/main.py | 13 +- setup.py | 72 +- .../alerts/app_distribution_fn.py | 46 +- src/firebase_functions/alerts/billing_fn.py | 46 +- .../alerts/crashlytics_fn.py | 65 +- .../alerts/performance_fn.py | 23 +- src/firebase_functions/alerts_fn.py | 6 +- src/firebase_functions/core.py | 11 +- src/firebase_functions/db_fn.py | 13 +- src/firebase_functions/eventarc_fn.py | 12 +- src/firebase_functions/firestore_fn.py | 96 +- src/firebase_functions/https_fn.py | 90 +- src/firebase_functions/identity_fn.py | 10 +- src/firebase_functions/logger.py | 22 +- src/firebase_functions/options.py | 220 +- src/firebase_functions/params.py | 38 +- src/firebase_functions/private/_alerts_fn.py | 37 +- .../private/_identity_fn.py | 112 +- src/firebase_functions/private/manifest.py | 116 +- .../private/path_pattern.py | 22 +- src/firebase_functions/private/serving.py | 8 +- .../private/token_verifier.py | 181 +- src/firebase_functions/private/util.py | 48 +- src/firebase_functions/pubsub_fn.py | 10 +- src/firebase_functions/remote_config_fn.py | 7 +- src/firebase_functions/scheduler_fn.py | 5 +- src/firebase_functions/storage_fn.py | 27 +- src/firebase_functions/tasks_fn.py | 4 +- src/firebase_functions/test_lab_fn.py | 14 +- tests/test_db.py | 28 +- tests/test_eventarc_fn.py | 1 + tests/test_firestore_fn.py | 97 +- tests/test_https_fn.py | 8 +- tests/test_identity_fn.py | 20 +- tests/test_init.py | 1 - tests/test_logger.py | 97 +- tests/test_manifest.py | 97 +- tests/test_options.py | 108 +- tests/test_params.py | 143 +- tests/test_path_pattern.py | 18 +- tests/test_pubsub_fn.py | 28 +- tests/test_remote_config_fn.py | 14 +- tests/test_scheduler_fn.py | 33 +- tests/test_storage_fn.py | 25 +- tests/test_tasks_fn.py | 9 +- tests/test_test_lab_fn.py | 31 +- tests/test_util.py | 82 +- uv.lock | 1812 +++++++++++++++++ 59 files changed, 3331 insertions(+), 1281 deletions(-) create mode 100644 async.md create mode 100644 modernization.md create mode 100644 uv.lock diff --git a/async.md b/async.md new file mode 100644 index 0000000..67e62f0 --- /dev/null +++ b/async.md @@ -0,0 +1,248 @@ +# Async Support for Firebase Functions Python + +## Overview + +This document outlines the design and implementation plan for adding async function support to firebase-functions-python. The goal is to leverage the new async capabilities in functions-framework while maintaining full backward compatibility with existing sync functions. + +## Background + +Functions-framework recently added async support via the `--asgi` flag, allowing async functions to be defined like: + +```python +import functions_framework.aio + +@functions_framework.aio.http +async def hello_async(request): # Starlette.Request + await asyncio.sleep(1) + return "Hello, async world!" +``` + +## Design Goals + +1. **No code duplication** - Reuse existing decorators and logic +2. **Backward compatibility** - All existing sync functions must continue to work +3. **Unified API** - Users shouldn't need different decorators for sync vs async +4. **Type safety** - Proper typing for both sync and async cases +5. **Automatic detection** - The system should automatically detect and handle async functions +6. **Universal support** - Async should work for ALL function types, not just HTTP + +## Function Types to Support + +Firebase Functions Python supports multiple trigger types that all need async support: + +### 1. HTTP Functions +- `@https_fn.on_request()` - Raw HTTP requests +- `@https_fn.on_call()` - Callable functions with auth/validation + +### 2. Firestore Functions +- `@firestore_fn.on_document_created()` +- `@firestore_fn.on_document_updated()` +- `@firestore_fn.on_document_deleted()` +- `@firestore_fn.on_document_written()` + +### 3. Realtime Database Functions +- `@db_fn.on_value_created()` +- `@db_fn.on_value_updated()` +- `@db_fn.on_value_deleted()` +- `@db_fn.on_value_written()` + +### 4. Cloud Storage Functions +- `@storage_fn.on_object_archived()` +- `@storage_fn.on_object_deleted()` +- `@storage_fn.on_object_finalized()` +- `@storage_fn.on_object_metadata_updated()` + +### 5. Pub/Sub Functions +- `@pubsub_fn.on_message_published()` + +### 6. Scheduler Functions +- `@scheduler_fn.on_schedule()` + +### 7. Task Queue Functions +- `@tasks_fn.on_task_dispatched()` + +### 8. EventArc Functions +- `@eventarc_fn.on_custom_event_published()` + +### 9. Remote Config Functions +- `@remote_config_fn.on_config_updated()` + +### 10. Test Lab Functions +- `@test_lab_fn.on_test_matrix_completed()` + +### 11. Alerts Functions +- Various alert triggers for billing, crashlytics, performance, etc. + +### 12. Identity Functions +- `@identity_fn.before_user_created()` +- `@identity_fn.before_user_signed_in()` + +## Implementation Strategy + +### Phase 1: Core Infrastructure + +#### 1.1 Async Detection Mechanism +- Add utility function to detect if a function is async using `inspect.iscoroutinefunction()` +- This detection should happen at decoration time + +#### 1.2 Metadata Storage +- Extend the `__firebase_endpoint__` attribute to include runtime mode information +- Add a field to `ManifestEndpoint` to indicate async functions: + ```python + @dataclasses.dataclass(frozen=True) + class ManifestEndpoint: + # ... existing fields ... + runtime_mode: Literal["sync", "async"] | None = "sync" + ``` + +#### 1.3 Type System Updates +- Create type unions to handle both sync and async cases +- For HTTP functions: + - Sync: `flask.Request` and `flask.Response` + - Async: `starlette.requests.Request` and response types +- For event functions: + - Both sync and async will receive the same event objects + - The difference is whether the handler is async + +### Phase 2: Decorator Updates + +#### 2.1 Universal Decorator Pattern +Each decorator should follow this pattern: + +```python +def on_some_event(**kwargs): + def decorator(func): + is_async = inspect.iscoroutinefunction(func) + + if is_async: + # Set up async wrapper + @functools.wraps(func) + async def async_wrapper(*args, **kwargs): + # Any necessary async setup + return await func(*args, **kwargs) + + wrapped = async_wrapper + runtime_mode = "async" + else: + # Use existing sync wrapper + wrapped = existing_sync_wrapper(func) + runtime_mode = "sync" + + # Set metadata + endpoint = create_endpoint( + # ... existing endpoint config ... + runtime_mode=runtime_mode + ) + _util.set_func_endpoint_attr(wrapped, endpoint) + + return wrapped + + return decorator +``` + +#### 2.2 HTTP Functions Special Handling +HTTP functions need special care because the request type changes: +- Sync: `flask.Request` +- Async: `starlette.requests.Request` + +We'll need to handle this in the type system and potentially in request processing. + +### Phase 3: Manifest and Deployment + +#### 3.1 Manifest Generation +- Update `serving.py` to include runtime mode in the manifest +- The functions.yaml should indicate which functions need async runtime + +#### 3.2 Firebase CLI Integration +- The CLI needs to read the runtime mode from the manifest +- When deploying async functions, it should: + - Set appropriate environment variables + - Pass the `--asgi` flag to functions-framework + - Potentially use different container configurations + +### Phase 4: Testing and Validation + +#### 4.1 Test Coverage +- Add async versions of existing tests +- Test mixed deployments (both sync and async functions) +- Verify proper error handling in async contexts +- Test timeout behavior for async functions + +#### 4.2 Example Updates +- Update examples to show async usage +- Create migration guide for converting sync to async + +## Example Usage + +### HTTP Functions +```python +# Sync (existing) +@https_fn.on_request() +def sync_http(request: Request) -> Response: + return Response("Hello sync") + +# Async (new) +@https_fn.on_request() +async def async_http(request) -> Response: # Will be Starlette Request + result = await some_async_api_call() + return Response(f"Hello async: {result}") +``` + +### Firestore Functions +```python +# Sync (existing) +@firestore_fn.on_document_created(document="users/{userId}") +def sync_user_created(event: Event[DocumentSnapshot]) -> None: + print(f"User created: {event.data.id}") + +# Async (new) +@firestore_fn.on_document_created(document="users/{userId}") +async def async_user_created(event: Event[DocumentSnapshot]) -> None: + await send_welcome_email(event.data.get("email")) + await update_analytics(event.data.id) +``` + +### Pub/Sub Functions +```python +# Async (new) +@pubsub_fn.on_message_published(topic="process-queue") +async def async_process_message(event: CloudEvent[MessagePublishedData]) -> None: + message = event.data.message + await process_job(message.data) +``` + +## Benefits + +1. **Performance**: Async functions can handle I/O-bound operations more efficiently +2. **Scalability**: Better resource utilization for functions that make external API calls +3. **Modern Python**: Aligns with Python's async/await ecosystem +4. **Flexibility**: Users can choose sync or async based on their needs + +## Considerations + +1. **Cold Start**: Need to verify async functions don't increase cold start times +2. **Memory Usage**: Monitor if async runtime uses more memory +3. **Debugging**: Ensure stack traces and error messages are clear for async functions +4. **Timeouts**: Verify timeout behavior works correctly with async functions + +## Migration Path + +1. Start with HTTP functions as proof of concept +2. Extend to event-triggered functions +3. Update documentation and examples +4. Release as minor version update (backward compatible) + +## Open Questions + +1. Should we support both Flask and Starlette response types for async HTTP functions? +2. How should we handle async context managers and cleanup? +3. Should we provide async versions of Firebase Admin SDK operations? +4. What's the best way to handle errors in async functions? + +## Next Steps + +1. Prototype async support for HTTP functions +2. Test with functions-framework in ASGI mode +3. Design type system for handling both sync and async +4. Update manifest generation +5. Coordinate with Firebase CLI team for deployment support \ No newline at end of file diff --git a/docs/theme/devsite_translator/html.py b/docs/theme/devsite_translator/html.py index 1e2553c..227e678 100755 --- a/docs/theme/devsite_translator/html.py +++ b/docs/theme/devsite_translator/html.py @@ -16,16 +16,16 @@ from sphinx.writers import html _DESCTYPE_NAMES = { - 'class': 'Classes', - 'data': 'Constants', - 'function': 'Functions', - 'method': 'Methods', - 'attribute': 'Attributes', - 'exception': 'Exceptions' + "class": "Classes", + "data": "Constants", + "function": "Functions", + "method": "Methods", + "attribute": "Attributes", + "exception": "Exceptions", } # Use the default translator for these node types -_RENDER_WITH_DEFAULT = ['method', 'staticmethod', 'attribute'] +_RENDER_WITH_DEFAULT = ["method", "staticmethod", "attribute"] class FiresiteHTMLTranslator(html.HTMLTranslator): @@ -39,83 +39,80 @@ class FiresiteHTMLTranslator(html.HTMLTranslator): def __init__(self, builder, *args, **kwds): html.HTMLTranslator.__init__(self, builder, *args, **kwds) - self.current_section = 'intro' + self.current_section = "intro" # This flag gets set to True at the start of a new 'section' tag, and then # back to False after the first object signature in the section is processed self.insert_header = False def visit_desc(self, node): - if node.parent.tagname == 'section': + if node.parent.tagname == "section": self.insert_header = True - if node['desctype'] != self.current_section: - self.body.append( - f"

{_DESCTYPE_NAMES[node['desctype']]}

") - self.current_section = node['desctype'] - if node['desctype'] in _RENDER_WITH_DEFAULT: + if node["desctype"] != self.current_section: + self.body.append(f"

{_DESCTYPE_NAMES[node['desctype']]}

") + self.current_section = node["desctype"] + if node["desctype"] in _RENDER_WITH_DEFAULT: html.HTMLTranslator.visit_desc(self, node) else: - self.body.append(self.starttag(node, 'table', - CLASS=node['objtype'])) + self.body.append(self.starttag(node, "table", CLASS=node["objtype"])) def depart_desc(self, node): - if node['desctype'] in _RENDER_WITH_DEFAULT: + if node["desctype"] in _RENDER_WITH_DEFAULT: html.HTMLTranslator.depart_desc(self, node) else: - self.body.append('\n\n') + self.body.append("\n\n") def visit_desc_signature(self, node): - if node.parent['desctype'] in _RENDER_WITH_DEFAULT: + if node.parent["desctype"] in _RENDER_WITH_DEFAULT: html.HTMLTranslator.visit_desc_signature(self, node) else: - self.body.append('') - self.body.append(self.starttag(node, 'th')) + self.body.append("") + self.body.append(self.starttag(node, "th")) if self.insert_header: - self.body.append( - f"

{node['fullname']}

") + self.body.append(f'

{node["fullname"]}

') self.insert_header = False def depart_desc_signature(self, node): - if node.parent['desctype'] in _RENDER_WITH_DEFAULT: + if node.parent["desctype"] in _RENDER_WITH_DEFAULT: html.HTMLTranslator.depart_desc_signature(self, node) else: - self.body.append('') + self.body.append("") def visit_desc_content(self, node): - if node.parent['desctype'] in _RENDER_WITH_DEFAULT: + if node.parent["desctype"] in _RENDER_WITH_DEFAULT: html.HTMLTranslator.visit_desc_content(self, node) else: - self.body.append('') - self.body.append(self.starttag(node, 'td')) + self.body.append("") + self.body.append(self.starttag(node, "td")) def depart_desc_content(self, node): - if node.parent['desctype'] in _RENDER_WITH_DEFAULT: + if node.parent["desctype"] in _RENDER_WITH_DEFAULT: html.HTMLTranslator.depart_desc_content(self, node) else: - self.body.append('') + self.body.append("") def visit_title(self, node): - if node.parent.tagname == 'section': + if node.parent.tagname == "section": self.body.append('

') else: html.HTMLTranslator.visit_title(self, node) def depart_title(self, node): - if node.parent.tagname == 'section': - self.body.append('

') + if node.parent.tagname == "section": + self.body.append("") else: html.HTMLTranslator.depart_title(self, node) def visit_note(self, node): - self.body.append(self.starttag(node, 'aside', CLASS='note')) + self.body.append(self.starttag(node, "aside", CLASS="note")) def depart_note(self, node): # pylint: disable=unused-argument - self.body.append('\n\n') + self.body.append("\n\n") def visit_warning(self, node): - self.body.append(self.starttag(node, 'aside', CLASS='caution')) + self.body.append(self.starttag(node, "aside", CLASS="caution")) def depart_warning(self, node): # pylint: disable=unused-argument - self.body.append('\n\n') + self.body.append("\n\n") diff --git a/example/functions/main.py b/example/functions/main.py index 5ac35f3..1de863a 100644 --- a/example/functions/main.py +++ b/example/functions/main.py @@ -1,6 +1,7 @@ """ Example Firebase Functions written in Python """ + from firebase_functions import https_fn, options, params, pubsub_fn from firebase_admin import initialize_app @@ -31,7 +32,8 @@ def oncallexample(req: https_fn.CallableRequest): return "Hello from https on call function example" -@pubsub_fn.on_message_published(topic="hello",) -def onmessagepublishedexample( - event: pubsub_fn.CloudEvent[pubsub_fn.MessagePublishedData]) -> None: +@pubsub_fn.on_message_published( + topic="hello", +) +def onmessagepublishedexample(event: pubsub_fn.CloudEvent[pubsub_fn.MessagePublishedData]) -> None: print("Hello from pubsub event:", event) diff --git a/modernization.md b/modernization.md new file mode 100644 index 0000000..830c62e --- /dev/null +++ b/modernization.md @@ -0,0 +1,160 @@ +# Firebase Functions Python Repository Modernization Plan + +## Overview +Modernize the Firebase Functions Python SDK to use contemporary Python tooling while maintaining compatibility with existing release processes and improving developer experience. + +## 1. **Migrate to Modern pyproject.toml Packaging** +- Convert setup.py to a modern pyproject.toml using setuptools build backend +- Keep dynamic version reading from `__init__.py` (required by release process) +- Preserve all metadata, classifiers, and package configuration +- Use PEP 621 standard for metadata +- Configure build backend: `[build-system]` with setuptools +- Remove setup.py entirely after migration + +## 2. **Adopt uv for Development** +- Replace pip/venv with uv for faster dependency management +- Define all dependencies in pyproject.toml: + ```toml + [project.dependencies] # Runtime dependencies + [project.optional-dependencies] + dev = [...] # All dev dependencies + test = [...] # Test-only dependencies + docs = [...] # Documentation dependencies + ``` +- Generate uv.lock for reproducible builds +- Add .python-version file set to 3.10 + +## 3. **Replace pylint/YAPF with ruff** +- Configure ruff to match current Google style guide: + - Line length: 100 + - Indentation: 4 spaces + - Enable equivalent pylint rules +- Remove .pylintrc and yapf configuration +- Add comprehensive ruff configuration in pyproject.toml +- Set target-version = "py310" for compatibility checks + +## 4. **Consider tox for Testing** +Yes, add tox for: +- Testing across Python 3.10, 3.11, 3.12 +- Running different test environments (unit, integration) +- Linting/formatting environments +- Documentation building +- Type checking +- Package building verification + +Benefits: +- Ensures compatibility across Python versions +- Isolated test environments +- Reproducible testing +- Can be used both locally and in CI + +## 5. **Add Modern Development Tools** +### Pre-commit hooks: +- ruff format and lint checks +- mypy type checking +- Check merge conflicts +- Trailing whitespace +- End of file fixer +- Check added large files +- Validate pyproject.toml + +### Development scripts: +Add convenience commands via: +- justfile for task automation +- Or pyproject.toml scripts section +- Common tasks: test, lint, format, typecheck, docs + +## 6. **Update CI/CD Workflows** + +### CI Workflow Updates: +- Install uv in GitHub Actions +- Use `uv sync` instead of `pip install` +- Replace pylint with `ruff check` +- Replace yapf with `ruff format --check` +- Use uv's built-in venv management +- Add dependency caching for uv + +### Release Workflow Updates: +**Critical**: The release process depends on: +1. Reading version from `src/firebase_functions/__init__.py` +2. Building with `python setup.py bdist_wheel sdist` +3. Specific artifact naming patterns + +**Changes needed**: +- Replace `python setup.py bdist_wheel sdist` with `python -m build` +- Ensure pyproject.toml maintains dynamic version reading +- Keep artifact names identical (firebase_functions-{version}-py3-none-any.whl) +- No changes needed to publish_preflight_check.sh + +## 7. **Migration Strategy** + +### Phase 1: Add new tooling (non-breaking) +1. Create comprehensive pyproject.toml +2. Install and configure uv +3. Set up ruff configuration +4. Add tox.ini +5. Configure pre-commit + +### Phase 2: Update workflows +1. Update local development docs +2. Modify CI to use new tools +3. Update release workflow for modern build + +### Phase 3: Remove old tooling +1. Remove setup.py +2. Delete .pylintrc +3. Remove yapf config from current pyproject.toml +4. Clean up old dependencies + +## 8. **File Structure Changes** + +### New files: +- `pyproject.toml` (expanded with all config) +- `uv.lock` +- `.python-version` +- `tox.ini` +- `.pre-commit-config.yaml` +- `justfile` (optional) + +### Modified files: +- `.github/workflows/ci.yaml` +- `.github/workflows/release.yaml` +- `.gitignore` (add uv directories) + +### Removed files: +- `setup.py` +- `.pylintrc` +- `pytest.ini` (migrate to pyproject.toml) + +## 9. **Compatibility Considerations** + +- Maintain Python 3.10+ requirement +- Keep all existing package metadata +- Preserve version reading mechanism +- Ensure built artifacts remain identical +- No changes to API or functionality +- Support existing installation methods + +## 10. **Benefits Summary** + +- **10-100x faster**: uv for dependency management +- **Faster linting**: ruff vs pylint/yapf +- **Modern standards**: PEP 517/518/621 compliance +- **Better reproducibility**: Lock files +- **Improved DX**: Pre-commit hooks, better tooling +- **Multi-version testing**: tox integration +- **Simpler configuration**: Everything in pyproject.toml + +## Implementation Steps + +1. Save this plan as modernization.md +2. Create new pyproject.toml with all configurations +3. Set up uv and generate lock file +4. Configure and test ruff +5. Add tox configuration +6. Set up pre-commit hooks +7. Update CI/CD workflows +8. Remove old configuration files +9. Update documentation + +This plan modernizes the tooling while maintaining full compatibility with the existing release process and Firebase ecosystem requirements. \ No newline at end of file diff --git a/samples/basic_alerts/functions/main.py b/samples/basic_alerts/functions/main.py index 49aaf88..868cc0b 100644 --- a/samples/basic_alerts/functions/main.py +++ b/samples/basic_alerts/functions/main.py @@ -7,30 +7,25 @@ from firebase_functions.alerts import performance_fn -@alerts_fn.on_alert_published( - alert_type=alerts_fn.AlertType.BILLING_PLAN_UPDATE) +@alerts_fn.on_alert_published(alert_type=alerts_fn.AlertType.BILLING_PLAN_UPDATE) def onalertpublished( - alert: alerts_fn.AlertEvent[alerts_fn.FirebaseAlertData[ - billing_fn.PlanUpdatePayload]] + alert: alerts_fn.AlertEvent[alerts_fn.FirebaseAlertData[billing_fn.PlanUpdatePayload]], ) -> None: print(alert) @app_distribution_fn.on_in_app_feedback_published() -def appdistributioninappfeedback( - alert: app_distribution_fn.InAppFeedbackEvent) -> None: +def appdistributioninappfeedback(alert: app_distribution_fn.InAppFeedbackEvent) -> None: print(alert) @app_distribution_fn.on_new_tester_ios_device_published() -def appdistributionnewrelease( - alert: app_distribution_fn.NewTesterDeviceEvent) -> None: +def appdistributionnewrelease(alert: app_distribution_fn.NewTesterDeviceEvent) -> None: print(alert) @billing_fn.on_plan_automated_update_published() -def billingautomatedplanupdate( - alert: billing_fn.BillingPlanAutomatedUpdateEvent) -> None: +def billingautomatedplanupdate(alert: billing_fn.BillingPlanAutomatedUpdateEvent) -> None: print(alert) @@ -40,42 +35,35 @@ def billingplanupdate(alert: billing_fn.BillingPlanUpdateEvent) -> None: @crashlytics_fn.on_new_fatal_issue_published() -def crashlyticsnewfatalissue( - alert: crashlytics_fn.CrashlyticsNewFatalIssueEvent) -> None: +def crashlyticsnewfatalissue(alert: crashlytics_fn.CrashlyticsNewFatalIssueEvent) -> None: print(alert) @crashlytics_fn.on_new_nonfatal_issue_published() -def crashlyticsnewnonfatalissue( - alert: crashlytics_fn.CrashlyticsNewNonfatalIssueEvent) -> None: +def crashlyticsnewnonfatalissue(alert: crashlytics_fn.CrashlyticsNewNonfatalIssueEvent) -> None: print(alert) @crashlytics_fn.on_new_anr_issue_published() -def crashlyticsnewanrissue( - alert: crashlytics_fn.CrashlyticsNewAnrIssueEvent) -> None: +def crashlyticsnewanrissue(alert: crashlytics_fn.CrashlyticsNewAnrIssueEvent) -> None: print(alert) @crashlytics_fn.on_regression_alert_published() -def crashlyticsregression( - alert: crashlytics_fn.CrashlyticsRegressionAlertEvent) -> None: +def crashlyticsregression(alert: crashlytics_fn.CrashlyticsRegressionAlertEvent) -> None: print(alert) @crashlytics_fn.on_stability_digest_published() -def crashlyticsstabilitydigest( - alert: crashlytics_fn.CrashlyticsStabilityDigestEvent) -> None: +def crashlyticsstabilitydigest(alert: crashlytics_fn.CrashlyticsStabilityDigestEvent) -> None: print(alert) @crashlytics_fn.on_velocity_alert_published() -def crashlyticsvelocity( - alert: crashlytics_fn.CrashlyticsVelocityAlertEvent) -> None: +def crashlyticsvelocity(alert: crashlytics_fn.CrashlyticsVelocityAlertEvent) -> None: print(alert) @performance_fn.on_threshold_alert_published() -def performancethreshold( - alert: performance_fn.PerformanceThresholdAlertEvent) -> None: +def performancethreshold(alert: performance_fn.PerformanceThresholdAlertEvent) -> None: print(alert) diff --git a/samples/basic_db/functions/main.py b/samples/basic_db/functions/main.py index 7fd134e..ff6bfc0 100644 --- a/samples/basic_db/functions/main.py +++ b/samples/basic_db/functions/main.py @@ -1,6 +1,7 @@ """ Example Firebase Functions for RTDB written in Python """ + from firebase_functions import db_fn, options from firebase_admin import initialize_app diff --git a/samples/basic_eventarc/functions/main.py b/samples/basic_eventarc/functions/main.py index 6f716fa..a471b48 100644 --- a/samples/basic_eventarc/functions/main.py +++ b/samples/basic_eventarc/functions/main.py @@ -1,9 +1,11 @@ """Firebase Cloud Functions for Eventarc triggers example.""" + from firebase_functions import eventarc_fn @eventarc_fn.on_custom_event_published( - event_type="firebase.extensions.storage-resize-images.v1.complete",) + event_type="firebase.extensions.storage-resize-images.v1.complete", +) def onimageresize(event: eventarc_fn.CloudEvent) -> None: """ Handle image resize events from the Firebase Storage Resize Images extension. diff --git a/samples/basic_firestore/functions/main.py b/samples/basic_firestore/functions/main.py index 7703ee6..b81be25 100644 --- a/samples/basic_firestore/functions/main.py +++ b/samples/basic_firestore/functions/main.py @@ -1,6 +1,7 @@ """ Example Firebase Functions for Firestore written in Python """ + from firebase_functions import firestore_fn, options from firebase_admin import initialize_app @@ -10,8 +11,7 @@ @firestore_fn.on_document_written(document="hello/{world}") -def onfirestoredocumentwritten( - event: firestore_fn.Event[firestore_fn.Change]) -> None: +def onfirestoredocumentwritten(event: firestore_fn.Event[firestore_fn.Change]) -> None: print("Hello from Firestore document write event:", event) @@ -26,6 +26,5 @@ def onfirestoredocumentdeleted(event: firestore_fn.Event) -> None: @firestore_fn.on_document_updated(document="hello/world") -def onfirestoredocumentupdated( - event: firestore_fn.Event[firestore_fn.Change]) -> None: +def onfirestoredocumentupdated(event: firestore_fn.Event[firestore_fn.Change]) -> None: print("Hello from Firestore document updated event:", event) diff --git a/samples/basic_params/functions/main.py b/samples/basic_params/functions/main.py index 731e77b..5a48802 100644 --- a/samples/basic_params/functions/main.py +++ b/samples/basic_params/functions/main.py @@ -1,6 +1,7 @@ """ Example Function params & inputs. """ + from firebase_functions import storage_fn, params from firebase_admin import initialize_app @@ -17,13 +18,11 @@ output_path = params.StringParam( "OUTPUT_PATH", label="storage bucket output path", - description= - "The path of in the bucket where processed images will be stored.", + description="The path of in the bucket where processed images will be stored.", input=params.TextInput( example="/images/processed", validation_regex=r"^\/.*$", - validation_error_message= - "Must be a valid path starting with a forward slash", + validation_error_message="Must be a valid path starting with a forward slash", ), default="/images/processed", ) @@ -32,23 +31,26 @@ "IMAGE_TYPE", label="convert image to preferred types", description="The image types you'd like your source image to convert to.", - input=params.MultiSelectInput([ - params.SelectOption(value="jpeg", label="jpeg"), - params.SelectOption(value="png", label="png"), - params.SelectOption(value="webp", label="webp"), - ]), + input=params.MultiSelectInput( + [ + params.SelectOption(value="jpeg", label="jpeg"), + params.SelectOption(value="png", label="png"), + params.SelectOption(value="webp", label="webp"), + ] + ), default=["jpeg", "png"], ) delete_original = params.BoolParam( "DELETE_ORIGINAL_FILE", label="delete the original file", - description= - "Do you want to automatically delete the original file from the Cloud Storage?", - input=params.SelectInput([ - params.SelectOption(value=True, label="Delete on any resize attempt"), - params.SelectOption(value=False, label="Don't delete"), - ],), + description="Do you want to automatically delete the original file from the Cloud Storage?", + input=params.SelectInput( + [ + params.SelectOption(value=True, label="Delete on any resize attempt"), + params.SelectOption(value=False, label="Don't delete"), + ], + ), default=True, ) diff --git a/samples/basic_tasks/functions/main.py b/samples/basic_tasks/functions/main.py index da5f922..5063e6c 100644 --- a/samples/basic_tasks/functions/main.py +++ b/samples/basic_tasks/functions/main.py @@ -50,14 +50,12 @@ def enqueuetask(req: https_fn.Request) -> https_fn.Response: "http_request": { "http_method": tasks_v2.HttpMethod.POST, "url": url, - "headers": { - "Content-type": "application/json" - }, + "headers": {"Content-type": "application/json"}, "body": json.dumps(body).encode(), }, - "schedule_time": - datetime.datetime.utcnow() + datetime.timedelta(minutes=1), - }) + "schedule_time": datetime.datetime.utcnow() + datetime.timedelta(minutes=1), + } + ) parent = client.queue_path( app.project_id, diff --git a/samples/basic_test_lab/functions/main.py b/samples/basic_test_lab/functions/main.py index 49f766e..b8c5cba 100644 --- a/samples/basic_test_lab/functions/main.py +++ b/samples/basic_test_lab/functions/main.py @@ -1,4 +1,5 @@ """Firebase Cloud Functions for Test Lab.""" + from firebase_functions.test_lab_fn import ( CloudEvent, TestMatrixCompletedData, @@ -13,14 +14,10 @@ def testmatrixcompleted(event: CloudEvent[TestMatrixCompletedData]) -> None: print(f"Test Matrix Outcome Summary: {event.data.outcome_summary}") print("Result Storage:") - print( - f" Tool Results History: {event.data.result_storage.tool_results_history}" - ) + print(f" Tool Results History: {event.data.result_storage.tool_results_history}") print(f" Results URI: {event.data.result_storage.results_uri}") print(f" GCS Path: {event.data.result_storage.gcs_path}") - print( - f" Tool Results Execution: {event.data.result_storage.tool_results_execution}" - ) + print(f" Tool Results Execution: {event.data.result_storage.tool_results_execution}") print("Client Info:") print(f" Client: {event.data.client_info.client}") diff --git a/samples/identity/functions/main.py b/samples/identity/functions/main.py index 9fa44f1..409df24 100644 --- a/samples/identity/functions/main.py +++ b/samples/identity/functions/main.py @@ -1,4 +1,5 @@ """Firebase Cloud Functions for blocking auth functions example.""" + from firebase_functions import identity_fn @@ -8,15 +9,19 @@ refresh_token=True, ) def beforeusercreated( - event: identity_fn.AuthBlockingEvent + event: identity_fn.AuthBlockingEvent, ) -> identity_fn.BeforeCreateResponse | None: print(event) if not event.data.email: return None if "@cats.com" in event.data.email: - return identity_fn.BeforeCreateResponse(display_name="Meow!",) + return identity_fn.BeforeCreateResponse( + display_name="Meow!", + ) if "@dogs.com" in event.data.email: - return identity_fn.BeforeCreateResponse(display_name="Woof!",) + return identity_fn.BeforeCreateResponse( + display_name="Woof!", + ) return None @@ -26,7 +31,7 @@ def beforeusercreated( refresh_token=True, ) def beforeusersignedin( - event: identity_fn.AuthBlockingEvent + event: identity_fn.AuthBlockingEvent, ) -> identity_fn.BeforeSignInResponse | None: print(event) if not event.data.email: diff --git a/setup.py b/setup.py index bc0659c..bd8bfa6 100644 --- a/setup.py +++ b/setup.py @@ -14,56 +14,70 @@ """ Setup for Firebase Functions Python. """ + from os import path from setuptools import find_packages, setup install_requires = [ - 'flask>=2.1.2', 'functions-framework>=3.0.0', 'firebase-admin>=6.0.0', - 'pyyaml>=6.0', 'typing-extensions>=4.4.0', 'cloudevents>=1.2.0,<2.0.0', - 'flask-cors>=3.0.10', 'pyjwt[crypto]>=2.5.0', 'google-events==0.5.0', - 'google-cloud-firestore>=2.11.0' + "flask>=2.1.2", + "functions-framework>=3.0.0", + "firebase-admin>=6.0.0", + "pyyaml>=6.0", + "typing-extensions>=4.4.0", + "cloudevents>=1.2.0,<2.0.0", + "flask-cors>=3.0.10", + "pyjwt[crypto]>=2.5.0", + "google-events==0.5.0", + "google-cloud-firestore>=2.11.0", ] dev_requires = [ - 'pytest>=7.1.2', 'setuptools>=63.4.2', 'pylint>=2.16.1', - 'pytest-cov>=3.0.0', 'mypy>=1.0.0', 'sphinx>=6.1.3', - 'sphinxcontrib-napoleon>=0.7', 'toml>=0.10.2', - 'google-cloud-tasks>=2.13.1', 'ruff>=0.1.0' + "pytest>=7.1.2", + "setuptools>=63.4.2", + "pylint>=2.16.1", + "pytest-cov>=3.0.0", + "mypy>=1.0.0", + "sphinx>=6.1.3", + "sphinxcontrib-napoleon>=0.7", + "toml>=0.10.2", + "google-cloud-tasks>=2.13.1", + "ruff>=0.1.0", ] # Read in the package metadata per recommendations from: # https://packaging.python.org/guides/single-sourcing-package-version/ -init_path = path.join(path.dirname(path.abspath(__file__)), 'src', - 'firebase_functions', '__init__.py') +init_path = path.join( + path.dirname(path.abspath(__file__)), "src", "firebase_functions", "__init__.py" +) version = {} with open(init_path) as fp: exec(fp.read(), version) # pylint: disable=exec-used long_description = ( - 'The Firebase Functions Python SDK provides an SDK for defining' - ' Cloud Functions for Firebase.') + "The Firebase Functions Python SDK provides an SDK for defining Cloud Functions for Firebase." +) setup( - name='firebase_functions', - version=version['__version__'], - description='Firebase Functions Python SDK', + name="firebase_functions", + version=version["__version__"], + description="Firebase Functions Python SDK", long_description=long_description, - url='https://github.com/firebase/firebase-functions-python', - author='Firebase Team', - keywords=['firebase', 'functions', 'google', 'cloud'], - license='Apache License 2.0', + url="https://github.com/firebase/firebase-functions-python", + author="Firebase Team", + keywords=["firebase", "functions", "google", "cloud"], + license="Apache License 2.0", install_requires=install_requires, - extras_require={'dev': dev_requires}, - packages=find_packages(where='src'), - package_dir={'': 'src'}, + extras_require={"dev": dev_requires}, + packages=find_packages(where="src"), + package_dir={"": "src"}, include_package_data=True, - package_data={'firebase_functions': ['py.typed']}, - python_requires='>=3.10', + package_data={"firebase_functions": ["py.typed"]}, + python_requires=">=3.10", classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], ) diff --git a/src/firebase_functions/alerts/app_distribution_fn.py b/src/firebase_functions/alerts/app_distribution_fn.py index 5ff3a39..de73341 100644 --- a/src/firebase_functions/alerts/app_distribution_fn.py +++ b/src/firebase_functions/alerts/app_distribution_fn.py @@ -15,6 +15,7 @@ """ Cloud functions to handle Firebase App Distribution events from Firebase Alerts. """ + import dataclasses as _dataclasses import functools as _functools import typing as _typing @@ -127,8 +128,7 @@ class AppDistributionEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_in_app_feedback_published' functions. """ -OnNewTesterIosDevicePublishedCallable = _typing.Callable[[NewTesterDeviceEvent], - None] +OnNewTesterIosDevicePublishedCallable = _typing.Callable[[NewTesterDeviceEvent], None] """ The type of the callable for 'on_new_tester_ios_device_published' functions. """ @@ -141,9 +141,10 @@ class AppDistributionEvent(CloudEvent[FirebaseAlertData[T]]): @_util.copy_func_kwargs(AppDistributionOptions) def on_new_tester_ios_device_published( - **kwargs -) -> _typing.Callable[[OnNewTesterIosDevicePublishedCallable], - OnNewTesterIosDevicePublishedCallable]: + **kwargs, +) -> _typing.Callable[ + [OnNewTesterIosDevicePublishedCallable], OnNewTesterIosDevicePublishedCallable +]: """ Event handler which runs every time a new tester iOS device is added. @@ -151,7 +152,7 @@ def on_new_tester_ios_device_published( .. code-block:: python - import firebase_functions.alerts.app_distribution_fn as app_distribution_fn + import firebase_functions.alerts.app_distribution_fn as app_distribution_fn @app_distribution_fn.on_new_tester_ios_device_published() def example(alert: app_distribution_fn.NewTesterDeviceEvent) -> None: @@ -160,27 +161,28 @@ def example(alert: app_distribution_fn.NewTesterDeviceEvent) -> None: :param \\*\\*kwargs: Options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.AppDistributionOptions` :rtype: :exc:`typing.Callable` - \\[ - \\[ :exc:`firebase_functions.alerts.app_distribution_fn.NewTesterDeviceEvent` \\], - `None` + \\[ + \\[ :exc:`firebase_functions.alerts.app_distribution_fn.NewTesterDeviceEvent` \\], + `None` \\] A function that takes a NewTesterDeviceEvent and returns None. """ options = AppDistributionOptions(**kwargs) def on_new_tester_ios_device_published_inner_decorator( - func: OnNewTesterIosDevicePublishedCallable): - + func: OnNewTesterIosDevicePublishedCallable, + ): @_functools.wraps(func) def on_new_tester_ios_device_published_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import app_distribution_event_from_ce + func(app_distribution_event_from_ce(raw)) _util.set_func_endpoint_attr( on_new_tester_ios_device_published_wrapped, options._endpoint( func_name=func.__name__, - alert_type='appDistribution.newTesterIosDevice', + alert_type="appDistribution.newTesterIosDevice", ), ) return on_new_tester_ios_device_published_wrapped @@ -190,9 +192,8 @@ def on_new_tester_ios_device_published_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(AppDistributionOptions) def on_in_app_feedback_published( - **kwargs -) -> _typing.Callable[[OnInAppFeedbackPublishedCallable], - OnInAppFeedbackPublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnInAppFeedbackPublishedCallable], OnInAppFeedbackPublishedCallable]: """ Event handler which runs every time new feedback is received. @@ -200,7 +201,7 @@ def on_in_app_feedback_published( .. code-block:: python - import firebase_functions.alerts.app_distribution_fn as app_distribution_fn + import firebase_functions.alerts.app_distribution_fn as app_distribution_fn @app_distribution_fn.on_in_app_feedback_published() def example(alert: app_distribution_fn.InAppFeedbackEvent) -> None: @@ -209,27 +210,26 @@ def example(alert: app_distribution_fn.InAppFeedbackEvent) -> None: :param \\*\\*kwargs: Options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.AppDistributionOptions` :rtype: :exc:`typing.Callable` - \\[ - \\[ :exc:`firebase_functions.alerts.app_distribution_fn.InAppFeedbackEvent` \\], - `None` + \\[ + \\[ :exc:`firebase_functions.alerts.app_distribution_fn.InAppFeedbackEvent` \\], + `None` \\] A function that takes a NewTesterDeviceEvent and returns None. """ options = AppDistributionOptions(**kwargs) - def on_in_app_feedback_published_inner_decorator( - func: OnInAppFeedbackPublishedCallable): - + def on_in_app_feedback_published_inner_decorator(func: OnInAppFeedbackPublishedCallable): @_functools.wraps(func) def on_in_app_feedback_published_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import app_distribution_event_from_ce + func(app_distribution_event_from_ce(raw)) _util.set_func_endpoint_attr( on_in_app_feedback_published_wrapped, options._endpoint( func_name=func.__name__, - alert_type='appDistribution.inAppFeedback', + alert_type="appDistribution.inAppFeedback", ), ) return on_in_app_feedback_published_wrapped diff --git a/src/firebase_functions/alerts/billing_fn.py b/src/firebase_functions/alerts/billing_fn.py index 6c698ed..86c3600 100644 --- a/src/firebase_functions/alerts/billing_fn.py +++ b/src/firebase_functions/alerts/billing_fn.py @@ -15,6 +15,7 @@ """ Cloud functions to handle billing events from Firebase Alerts. """ + import dataclasses as _dataclasses import functools as _functools import typing as _typing @@ -85,8 +86,7 @@ class BillingEvent(CloudEvent[FirebaseAlertData[T]]): The type of the callable for 'on_plan_update_published' functions. """ -OnPlanAutomatedUpdatePublishedCallable = _typing.Callable[ - [BillingPlanAutomatedUpdateEvent], None] +OnPlanAutomatedUpdatePublishedCallable = _typing.Callable[[BillingPlanAutomatedUpdateEvent], None] """ The type of the callable for 'on_plan_automated_update_published' functions. """ @@ -94,9 +94,8 @@ class BillingEvent(CloudEvent[FirebaseAlertData[T]]): @_util.copy_func_kwargs(BillingOptions) def on_plan_update_published( - **kwargs -) -> _typing.Callable[[OnPlanUpdatePublishedCallable], - OnPlanUpdatePublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnPlanUpdatePublishedCallable], OnPlanUpdatePublishedCallable]: """ Event handler which triggers when a Firebase Alerts billing event is published. @@ -104,7 +103,7 @@ def on_plan_update_published( .. code-block:: python - import firebase_functions.alerts.billing_fn as billing_fn + import firebase_functions.alerts.billing_fn as billing_fn @billing_fn.on_plan_update_published() def example(alert: billing_fn.BillingPlanUpdateEvent) -> None: @@ -113,27 +112,26 @@ def example(alert: billing_fn.BillingPlanUpdateEvent) -> None: :param \\*\\*kwargs: Options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.BillingOptions` :rtype: :exc:`typing.Callable` - \\[ - \\[ :exc:`firebase_functions.alerts.billing_fn.BillingPlanUpdateEvent` \\], - `None` + \\[ + \\[ :exc:`firebase_functions.alerts.billing_fn.BillingPlanUpdateEvent` \\], + `None` \\] A function that takes a BillingPlanUpdateEvent and returns None. """ options = BillingOptions(**kwargs) - def on_plan_update_published_inner_decorator( - func: OnPlanUpdatePublishedCallable): - + def on_plan_update_published_inner_decorator(func: OnPlanUpdatePublishedCallable): @_functools.wraps(func) def on_plan_update_published_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import billing_event_from_ce + func(billing_event_from_ce(raw)) _util.set_func_endpoint_attr( on_plan_update_published_wrapped, options._endpoint( func_name=func.__name__, - alert_type='billing.planUpdate', + alert_type="billing.planUpdate", ), ) return on_plan_update_published_wrapped @@ -143,9 +141,10 @@ def on_plan_update_published_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(BillingOptions) def on_plan_automated_update_published( - **kwargs -) -> _typing.Callable[[OnPlanAutomatedUpdatePublishedCallable], - OnPlanAutomatedUpdatePublishedCallable]: + **kwargs, +) -> _typing.Callable[ + [OnPlanAutomatedUpdatePublishedCallable], OnPlanAutomatedUpdatePublishedCallable +]: """ Event handler which triggers when a Firebase Alerts billing event is published. @@ -153,7 +152,7 @@ def on_plan_automated_update_published( .. code-block:: python - import firebase_functions.alerts.billing_fn as billing_fn + import firebase_functions.alerts.billing_fn as billing_fn @billing_fn.on_plan_automated_update_published() def example(alert: billing_fn.BillingPlanAutomatedUpdateEvent) -> None: @@ -162,27 +161,28 @@ def example(alert: billing_fn.BillingPlanAutomatedUpdateEvent) -> None: :param \\*\\*kwargs: Options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.BillingOptions` :rtype: :exc:`typing.Callable` - \\[ - \\[ :exc:`firebase_functions.alerts.billing_fn.BillingPlanAutomatedUpdateEvent` \\], - `None` + \\[ + \\[ :exc:`firebase_functions.alerts.billing_fn.BillingPlanAutomatedUpdateEvent` \\], + `None` \\] A function that takes a BillingPlanUpdateEvent and returns None. """ options = BillingOptions(**kwargs) def on_plan_automated_update_published_inner_decorator( - func: OnPlanAutomatedUpdatePublishedCallable): - + func: OnPlanAutomatedUpdatePublishedCallable, + ): @_functools.wraps(func) def on_plan_automated_update_published_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import billing_event_from_ce + func(billing_event_from_ce(raw)) _util.set_func_endpoint_attr( on_plan_automated_update_published_wrapped, options._endpoint( func_name=func.__name__, - alert_type='billing.planAutomatedUpdate', + alert_type="billing.planAutomatedUpdate", ), ) return on_plan_automated_update_published_wrapped diff --git a/src/firebase_functions/alerts/crashlytics_fn.py b/src/firebase_functions/alerts/crashlytics_fn.py index 915c454..ee1fbd1 100644 --- a/src/firebase_functions/alerts/crashlytics_fn.py +++ b/src/firebase_functions/alerts/crashlytics_fn.py @@ -15,6 +15,7 @@ """ Cloud functions to handle Crashlytics events from Firebase Alerts. """ + import dataclasses as _dataclasses import typing as _typing import cloudevents.http as _ce @@ -220,8 +221,7 @@ class CrashlyticsEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_new_fatal_issue_published' functions. """ -OnNewFatalIssuePublishedCallable = _typing.Callable[ - [CrashlyticsNewFatalIssueEvent], None] +OnNewFatalIssuePublishedCallable = _typing.Callable[[CrashlyticsNewFatalIssueEvent], None] """ The type of the callable for 'on_new_fatal_issue_published' functions. """ @@ -231,8 +231,7 @@ class CrashlyticsEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_new_nonfatal_issue_published' functions. """ -OnNewNonfatalIssuePublishedCallable = _typing.Callable[ - [CrashlyticsNewNonfatalIssueEvent], None] +OnNewNonfatalIssuePublishedCallable = _typing.Callable[[CrashlyticsNewNonfatalIssueEvent], None] """ The type of the callable for 'on_new_nonfatal_issue_published' functions. """ @@ -242,8 +241,7 @@ class CrashlyticsEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_regression_alert_published' functions. """ -OnRegressionAlertPublishedCallable = _typing.Callable[ - [CrashlyticsRegressionAlertEvent], None] +OnRegressionAlertPublishedCallable = _typing.Callable[[CrashlyticsRegressionAlertEvent], None] """ The type of the callable for 'on_regression_alert_published' functions. """ @@ -253,8 +251,7 @@ class CrashlyticsEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_stability_digest_published' functions. """ -OnStabilityDigestPublishedCallable = _typing.Callable[ - [CrashlyticsStabilityDigestEvent], None] +OnStabilityDigestPublishedCallable = _typing.Callable[[CrashlyticsStabilityDigestEvent], None] """ The type of the callable for 'on_stability_digest_published' functions. """ @@ -264,8 +261,7 @@ class CrashlyticsEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_velocity_alert_published' functions. """ -OnVelocityAlertPublishedCallable = _typing.Callable[ - [CrashlyticsVelocityAlertEvent], None] +OnVelocityAlertPublishedCallable = _typing.Callable[[CrashlyticsVelocityAlertEvent], None] """ The type of the callable for 'on_velocity_alert_published' functions. """ @@ -275,8 +271,7 @@ class CrashlyticsEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_new_anr_issue_published' functions. """ -OnNewAnrIssuePublishedCallable = _typing.Callable[[CrashlyticsNewAnrIssueEvent], - None] +OnNewAnrIssuePublishedCallable = _typing.Callable[[CrashlyticsNewAnrIssueEvent], None] """ The type of the callable for 'on_new_anr_issue_published' functions. """ @@ -289,10 +284,10 @@ def _create_crashlytics_decorator( options = CrashlyticsOptions(**kwargs) def crashlytics_decorator_inner(func: _typing.Callable): - @_functools.wraps(func) def crashlytics_decorator_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import crashlytics_event_from_ce + func(crashlytics_event_from_ce(raw)) _util.set_func_endpoint_attr( @@ -309,9 +304,8 @@ def crashlytics_decorator_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(CrashlyticsOptions) def on_new_fatal_issue_published( - **kwargs -) -> _typing.Callable[[OnNewFatalIssuePublishedCallable], - OnNewFatalIssuePublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnNewFatalIssuePublishedCallable], OnNewFatalIssuePublishedCallable]: """ Event handler which runs every time a new fatal issue is received. @@ -334,14 +328,13 @@ def example(alert: crashlytics_fn.CrashlyticsNewFatalIssueEvent) -> None: \\] A function that takes a CrashlyticsNewFatalIssueEvent and returns None. """ - return _create_crashlytics_decorator('crashlytics.newFatalIssue', **kwargs) + return _create_crashlytics_decorator("crashlytics.newFatalIssue", **kwargs) @_util.copy_func_kwargs(CrashlyticsOptions) def on_new_nonfatal_issue_published( - **kwargs -) -> _typing.Callable[[OnNewNonfatalIssuePublishedCallable], - OnNewNonfatalIssuePublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnNewNonfatalIssuePublishedCallable], OnNewNonfatalIssuePublishedCallable]: """ Event handler which runs every time a new nonfatal issue is received. @@ -364,15 +357,13 @@ def example(alert: crashlytics_fn.CrashlyticsNewNonfatalIssueEvent) -> None: \\] A function that takes a CrashlyticsNewNonfatalIssueEvent and returns None. """ - return _create_crashlytics_decorator('crashlytics.newNonfatalIssue', - **kwargs) + return _create_crashlytics_decorator("crashlytics.newNonfatalIssue", **kwargs) @_util.copy_func_kwargs(CrashlyticsOptions) def on_regression_alert_published( - **kwargs -) -> _typing.Callable[[OnRegressionAlertPublishedCallable], - OnRegressionAlertPublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnRegressionAlertPublishedCallable], OnRegressionAlertPublishedCallable]: """ Event handler which runs every time a regression alert is received. @@ -395,14 +386,13 @@ def example(alert: crashlytics_fn.CrashlyticsRegressionAlertEvent) -> None: \\] A function that takes a CrashlyticsRegressionAlertEvent and returns None. """ - return _create_crashlytics_decorator('crashlytics.regression', **kwargs) + return _create_crashlytics_decorator("crashlytics.regression", **kwargs) @_util.copy_func_kwargs(CrashlyticsOptions) def on_stability_digest_published( - **kwargs -) -> _typing.Callable[[OnStabilityDigestPublishedCallable], - OnStabilityDigestPublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnStabilityDigestPublishedCallable], OnStabilityDigestPublishedCallable]: """ Event handler which runs every time a stability digest is received. @@ -425,15 +415,13 @@ def example(alert: crashlytics_fn.CrashlyticsStabilityDigestEvent) -> None: \\] A function that takes a CrashlyticsStabilityDigestEvent and returns None. """ - return _create_crashlytics_decorator('crashlytics.stabilityDigest', - **kwargs) + return _create_crashlytics_decorator("crashlytics.stabilityDigest", **kwargs) @_util.copy_func_kwargs(CrashlyticsOptions) def on_velocity_alert_published( - **kwargs -) -> _typing.Callable[[OnVelocityAlertPublishedCallable], - OnVelocityAlertPublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnVelocityAlertPublishedCallable], OnVelocityAlertPublishedCallable]: """ Event handler which runs every time a velocity alert is received. @@ -456,14 +444,13 @@ def example(alert: crashlytics_fn.CrashlyticsVelocityAlertEvent) -> None: \\] A function that takes a CrashlyticsVelocityAlertEvent and returns None. """ - return _create_crashlytics_decorator('crashlytics.velocity', **kwargs) + return _create_crashlytics_decorator("crashlytics.velocity", **kwargs) @_util.copy_func_kwargs(CrashlyticsOptions) def on_new_anr_issue_published( - **kwargs -) -> _typing.Callable[[OnNewAnrIssuePublishedCallable], - OnNewAnrIssuePublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnNewAnrIssuePublishedCallable], OnNewAnrIssuePublishedCallable]: """ Event handler which runs every time a new ANR issue is received. @@ -486,4 +473,4 @@ def example(alert: crashlytics_fn.CrashlyticsNewAnrIssueEvent) -> None: \\] A function that takes a CrashlyticsNewAnrIssueEvent and returns None. """ - return _create_crashlytics_decorator('crashlytics.newAnrIssue', **kwargs) + return _create_crashlytics_decorator("crashlytics.newAnrIssue", **kwargs) diff --git a/src/firebase_functions/alerts/performance_fn.py b/src/firebase_functions/alerts/performance_fn.py index ad00c6a..69cbeca 100644 --- a/src/firebase_functions/alerts/performance_fn.py +++ b/src/firebase_functions/alerts/performance_fn.py @@ -121,8 +121,7 @@ class PerformanceEvent(CloudEvent[FirebaseAlertData[T]]): The type of the event for 'on_threshold_alert_published' functions. """ -OnThresholdAlertPublishedCallable = _typing.Callable[ - [PerformanceThresholdAlertEvent], None] +OnThresholdAlertPublishedCallable = _typing.Callable[[PerformanceThresholdAlertEvent], None] """ The type of the callable for 'on_threshold_alert_published' functions. """ @@ -130,9 +129,8 @@ class PerformanceEvent(CloudEvent[FirebaseAlertData[T]]): @_util.copy_func_kwargs(PerformanceOptions) def on_threshold_alert_published( - **kwargs -) -> _typing.Callable[[OnThresholdAlertPublishedCallable], - OnThresholdAlertPublishedCallable]: + **kwargs, +) -> _typing.Callable[[OnThresholdAlertPublishedCallable], OnThresholdAlertPublishedCallable]: """ Event handler which runs every time a threshold alert is received. @@ -140,7 +138,7 @@ def on_threshold_alert_published( .. code-block:: python - import firebase_functions.alerts.performance_fn as performance_fn + import firebase_functions.alerts.performance_fn as performance_fn @performance_fn.on_threshold_alert_published() def example(alert: performance_fn.PerformanceThresholdAlertEvent) -> None: @@ -149,27 +147,26 @@ def example(alert: performance_fn.PerformanceThresholdAlertEvent) -> None: :param \\*\\*kwargs: Options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.PerformanceOptions` :rtype: :exc:`typing.Callable` - \\[ - \\[ :exc:`firebase_functions.alerts.performance_fn.PerformanceThresholdAlertEvent` \\], - `None` + \\[ + \\[ :exc:`firebase_functions.alerts.performance_fn.PerformanceThresholdAlertEvent` \\], + `None` \\] A function that takes a PerformanceThresholdAlertEvent and returns None. """ options = PerformanceOptions(**kwargs) - def on_threshold_alert_published_inner_decorator( - func: OnThresholdAlertPublishedCallable): - + def on_threshold_alert_published_inner_decorator(func: OnThresholdAlertPublishedCallable): @_functools.wraps(func) def on_threshold_alert_published_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import performance_event_from_ce + func(performance_event_from_ce(raw)) _util.set_func_endpoint_attr( on_threshold_alert_published_wrapped, options._endpoint( func_name=func.__name__, - alert_type='performance.threshold', + alert_type="performance.threshold", ), ) return on_threshold_alert_published_wrapped diff --git a/src/firebase_functions/alerts_fn.py b/src/firebase_functions/alerts_fn.py index ed68673..69f8a36 100644 --- a/src/firebase_functions/alerts_fn.py +++ b/src/firebase_functions/alerts_fn.py @@ -63,7 +63,7 @@ class AlertEvent(_CloudEvent[T]): @_util.copy_func_kwargs(FirebaseAlertOptions) def on_alert_published( - **kwargs + **kwargs, ) -> _typing.Callable[[OnAlertPublishedCallable], OnAlertPublishedCallable]: """ Event handler that triggers when a Firebase Alerts event is published. @@ -72,7 +72,7 @@ def on_alert_published( .. code-block:: python - from firebase_functions import alerts_fn + from firebase_functions import alerts_fn @alerts_fn.on_alert_published( alert_type=alerts_fn.AlertType.CRASHLYTICS_NEW_FATAL_ISSUE, @@ -91,10 +91,10 @@ def example(alert: alerts_fn.AlertEvent[alerts_fn.FirebaseAlertData]) -> None: options = FirebaseAlertOptions(**kwargs) def on_alert_published_inner_decorator(func: OnAlertPublishedCallable): - @_functools.wraps(func) def on_alert_published_wrapped(raw: _ce.CloudEvent): from firebase_functions.private._alerts_fn import alerts_event_from_ce + _with_init(func)(alerts_event_from_ce(raw)) _util.set_func_endpoint_attr( diff --git a/src/firebase_functions/core.py b/src/firebase_functions/core.py index a12e688..fb8dfd2 100644 --- a/src/firebase_functions/core.py +++ b/src/firebase_functions/core.py @@ -14,6 +14,7 @@ """ Public code that is shared across modules. """ + import dataclasses as _dataclass import datetime as _datetime import typing as _typing @@ -90,9 +91,9 @@ class Change(_typing.Generic[T]): def init(callback: _typing.Callable[[], _typing.Any]) -> None: """ - Registers a function that should be run when in a production environment - before executing any functions code. - Calling this decorator more than once leads to undefined behavior. + Registers a function that should be run when in a production environment + before executing any functions code. + Calling this decorator more than once leads to undefined behavior. """ global _did_init @@ -107,9 +108,7 @@ def init(callback: _typing.Callable[[], _typing.Any]) -> None: _did_init = False -def _with_init( - fn: _typing.Callable[..., - _typing.Any]) -> _typing.Callable[..., _typing.Any]: +def _with_init(fn: _typing.Callable[..., _typing.Any]) -> _typing.Callable[..., _typing.Any]: """ A decorator that runs the init callback before running the decorated function. """ diff --git a/src/firebase_functions/db_fn.py b/src/firebase_functions/db_fn.py index 7298e99..47bfabc 100644 --- a/src/firebase_functions/db_fn.py +++ b/src/firebase_functions/db_fn.py @@ -14,6 +14,7 @@ """ Module for Cloud Functions that are triggered by the Firebase Realtime Database. """ + # pylint: disable=protected-access import dataclasses as _dataclass import functools as _functools @@ -147,7 +148,8 @@ def example(event: Event[Change[object]]) -> None: def on_value_written_inner_decorator(func: _C1): ref_pattern = _path_pattern.PathPattern(options.reference) instance_pattern = _path_pattern.PathPattern( - options.instance if options.instance is not None else "*") + options.instance if options.instance is not None else "*" + ) @_functools.wraps(func) def on_value_written_wrapped(raw: _ce.CloudEvent): @@ -197,7 +199,8 @@ def example(event: Event[Change[object]]) -> None: def on_value_updated_inner_decorator(func: _C1): ref_pattern = _path_pattern.PathPattern(options.reference) instance_pattern = _path_pattern.PathPattern( - options.instance if options.instance is not None else "*") + options.instance if options.instance is not None else "*" + ) @_functools.wraps(func) def on_value_updated_wrapped(raw: _ce.CloudEvent): @@ -247,7 +250,8 @@ def example(event: Event[object]): def on_value_created_inner_decorator(func: _C2): ref_pattern = _path_pattern.PathPattern(options.reference) instance_pattern = _path_pattern.PathPattern( - options.instance if options.instance is not None else "*") + options.instance if options.instance is not None else "*" + ) @_functools.wraps(func) def on_value_created_wrapped(raw: _ce.CloudEvent): @@ -297,7 +301,8 @@ def example(event: Event[object]) -> None: def on_value_deleted_inner_decorator(func: _C2): ref_pattern = _path_pattern.PathPattern(options.reference) instance_pattern = _path_pattern.PathPattern( - options.instance if options.instance is not None else "*") + options.instance if options.instance is not None else "*" + ) @_functools.wraps(func) def on_value_deleted_wrapped(raw: _ce.CloudEvent): diff --git a/src/firebase_functions/eventarc_fn.py b/src/firebase_functions/eventarc_fn.py index d76772c..93a472d 100644 --- a/src/firebase_functions/eventarc_fn.py +++ b/src/firebase_functions/eventarc_fn.py @@ -26,9 +26,8 @@ @_util.copy_func_kwargs(_options.EventarcTriggerOptions) def on_custom_event_published( - **kwargs -) -> _typing.Callable[[_typing.Callable[[CloudEvent], None]], _typing.Callable[ - [CloudEvent], None]]: + **kwargs, +) -> _typing.Callable[[_typing.Callable[[CloudEvent], None]], _typing.Callable[[CloudEvent], None]]: """ Creates a handler for events published on the default event eventarc channel. @@ -52,9 +51,7 @@ def onimageresize(event: eventarc_fn.CloudEvent) -> None: """ options = _options.EventarcTriggerOptions(**kwargs) - def on_custom_event_published_decorator(func: _typing.Callable[[CloudEvent], - None]): - + def on_custom_event_published_decorator(func: _typing.Callable[[CloudEvent], None]): @_functools.wraps(func) def on_custom_event_published_wrapped(raw: _ce.CloudEvent): event_attributes = raw._get_attributes() @@ -65,8 +62,7 @@ def on_custom_event_published_wrapped(raw: _ce.CloudEvent): id=event_dict["id"], source=event_dict["source"], specversion=event_dict["specversion"], - subject=event_dict["subject"] - if "subject" in event_dict else None, + subject=event_dict["subject"] if "subject" in event_dict else None, time=_dt.datetime.strptime( event_dict["time"], "%Y-%m-%dT%H:%M:%S.%f%z", diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index a9d4f2a..878920f 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -14,6 +14,7 @@ """ Module for Cloud Functions that are triggered by Firestore. """ + # pylint: disable=protected-access import dataclasses as _dataclass import functools as _functools @@ -87,8 +88,7 @@ class Event(_core.CloudEvent[_core.T]): _C1 = _typing.Callable[[_E1], None] _C2 = _typing.Callable[[_E2], None] -AuthType = _typing.Literal["service_account", "api_key", "system", - "unauthenticated", "unknown"] +AuthType = _typing.Literal["service_account", "api_key", "system", "unauthenticated", "unknown"] @_dataclass.dataclass(frozen=True) @@ -117,17 +117,18 @@ def _firestore_endpoint_handler( content_type: str = event_attributes["datacontenttype"] if "application/json" in content_type or isinstance(event_data, dict): firestore_event_data = _typing.cast( - _firestore.DocumentEventData, - _firestore.DocumentEventData.from_json(event_data)) - elif "application/protobuf" in content_type or isinstance( - event_data, bytes): + _firestore.DocumentEventData, _firestore.DocumentEventData.from_json(event_data) + ) + elif "application/protobuf" in content_type or isinstance(event_data, bytes): firestore_event_data = _typing.cast( - _firestore.DocumentEventData, - _firestore.DocumentEventData.deserialize(event_data)) + _firestore.DocumentEventData, _firestore.DocumentEventData.deserialize(event_data) + ) else: actual_type = type(event_data) - raise TypeError(f"Firestore: Cannot parse event payload of data type " - f"'{actual_type}' and content type '{content_type}'.") + raise TypeError( + f"Firestore: Cannot parse event payload of data type " + f"'{actual_type}' and content type '{content_type}'." + ) event_location = event_attributes["location"] event_project = event_attributes["project"] @@ -141,15 +142,15 @@ def _firestore_endpoint_handler( if _DEFAULT_APP_NAME not in _apps: initialize_app() app = get_app() - firestore_client = _firestore_v1.Client(project=app.project_id, - database=event_database) + firestore_client = _firestore_v1.Client(project=app.project_id, database=event_database) firestore_ref: DocumentReference = firestore_client.document(event_document) value_snapshot: DocumentSnapshot | None = None old_value_snapshot: DocumentSnapshot | None = None if firestore_event_data.value: document_dict = _firestore_helpers.decode_dict( - firestore_event_data.value.fields, firestore_client) + firestore_event_data.value.fields, firestore_client + ) value_snapshot = _firestore_v1.DocumentSnapshot( firestore_ref, document_dict, @@ -160,7 +161,8 @@ def _firestore_endpoint_handler( ) if firestore_event_data.old_value: document_dict = _firestore_helpers.decode_dict( - firestore_event_data.old_value.fields, firestore_client) + firestore_event_data.old_value.fields, firestore_client + ) old_value_snapshot = _firestore_v1.DocumentSnapshot( firestore_ref, document_dict, @@ -170,23 +172,23 @@ def _firestore_endpoint_handler( firestore_event_data.old_value.update_time, ) - if event_type in (_event_type_deleted, - _event_type_deleted_with_auth_context): - firestore_event_data = _typing.cast(_firestore.DocumentEventData, - old_value_snapshot) - if event_type in (_event_type_created, - _event_type_created_with_auth_context): - firestore_event_data = _typing.cast(_firestore.DocumentEventData, - value_snapshot) - if event_type in (_event_type_written, _event_type_updated, - _event_type_written_with_auth_context, - _event_type_updated_with_auth_context): + if event_type in (_event_type_deleted, _event_type_deleted_with_auth_context): + firestore_event_data = _typing.cast(_firestore.DocumentEventData, old_value_snapshot) + if event_type in (_event_type_created, _event_type_created_with_auth_context): + firestore_event_data = _typing.cast(_firestore.DocumentEventData, value_snapshot) + if event_type in ( + _event_type_written, + _event_type_updated, + _event_type_written_with_auth_context, + _event_type_updated_with_auth_context, + ): firestore_event_data = _typing.cast( _firestore.DocumentEventData, Change( before=old_value_snapshot, after=value_snapshot, - )) + ), + ) params: dict[str, str] = { **document_pattern.extract_matches(event_document), @@ -213,9 +215,9 @@ def _firestore_endpoint_handler( if event_type.endswith(".withAuthContext"): event_auth_type = event_attributes["authtype"] event_auth_id = event_attributes["authid"] - database_event_with_auth_context = AuthEvent(**vars(database_event), - auth_type=event_auth_type, - auth_id=event_auth_id) + database_event_with_auth_context = AuthEvent( + **vars(database_event), auth_type=event_auth_type, auth_id=event_auth_id + ) func(database_event_with_auth_context) else: # mypy cannot infer that the event type is correct, hence the cast @@ -245,8 +247,7 @@ def example(event: Event[Change[DocumentSnapshot]]) -> None: options = FirestoreOptions(**kwargs) def on_document_written_inner_decorator(func: _C1): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_written_wrapped(raw: _ce.CloudEvent): @@ -271,8 +272,7 @@ def on_document_written_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) -def on_document_written_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: +def on_document_written_with_auth_context(**kwargs) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is created, updated, or deleted in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -296,8 +296,7 @@ def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None: options = FirestoreOptions(**kwargs) def on_document_written_with_auth_context_inner_decorator(func: _C1): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_written_with_auth_context_wrapped(raw: _ce.CloudEvent): @@ -344,8 +343,7 @@ def example(event: Event[Change[DocumentSnapshot]]) -> None: options = FirestoreOptions(**kwargs) def on_document_updated_inner_decorator(func: _C1): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_updated_wrapped(raw: _ce.CloudEvent): @@ -370,8 +368,7 @@ def on_document_updated_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) -def on_document_updated_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: +def on_document_updated_with_auth_context(**kwargs) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is updated in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -395,8 +392,7 @@ def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None: options = FirestoreOptions(**kwargs) def on_document_updated_with_auth_context_inner_decorator(func: _C1): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_updated_with_auth_context_wrapped(raw: _ce.CloudEvent): @@ -443,8 +439,7 @@ def example(event: Event[DocumentSnapshot]): options = FirestoreOptions(**kwargs) def on_document_created_inner_decorator(func: _C2): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_created_wrapped(raw: _ce.CloudEvent): @@ -469,8 +464,7 @@ def on_document_created_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) -def on_document_created_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: +def on_document_created_with_auth_context(**kwargs) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is created in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -494,8 +488,7 @@ def example(event: AuthEvent[DocumentSnapshot]): options = FirestoreOptions(**kwargs) def on_document_created_with_auth_context_inner_decorator(func: _C2): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_created_with_auth_context_wrapped(raw: _ce.CloudEvent): @@ -542,8 +535,7 @@ def example(event: Event[DocumentSnapshot]) -> None: options = FirestoreOptions(**kwargs) def on_document_deleted_inner_decorator(func: _C2): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_deleted_wrapped(raw: _ce.CloudEvent): @@ -568,8 +560,7 @@ def on_document_deleted_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) -def on_document_deleted_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: +def on_document_deleted_with_auth_context(**kwargs) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is deleted in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -593,8 +584,7 @@ def example(event: AuthEvent[DocumentSnapshot]) -> None: options = FirestoreOptions(**kwargs) def on_document_deleted_with_auth_context_inner_decorator(func: _C2): - document_pattern = _path_pattern.PathPattern( - _util.normalize_path(options.document)) + document_pattern = _path_pattern.PathPattern(_util.normalize_path(options.document)) @_functools.wraps(func) def on_document_deleted_with_auth_context_wrapped(raw: _ce.CloudEvent): diff --git a/src/firebase_functions/https_fn.py b/src/firebase_functions/https_fn.py index 10749e9..3aafb03 100644 --- a/src/firebase_functions/https_fn.py +++ b/src/firebase_functions/https_fn.py @@ -14,6 +14,7 @@ """Module for functions that listen to HTTPS endpoints. These can be raw web requests and Callable RPCs. """ + # pylint: disable=protected-access import dataclasses as _dataclasses import functools as _functools @@ -176,40 +177,35 @@ class _HttpErrorCode: _error_code_map = { - FunctionsErrorCode.OK: - _HttpErrorCode(_CanonicalErrorCodeName.OK, 200), - FunctionsErrorCode.CANCELLED: - _HttpErrorCode(_CanonicalErrorCodeName.CANCELLED, 499), - FunctionsErrorCode.UNKNOWN: - _HttpErrorCode(_CanonicalErrorCodeName.UNKNOWN, 500), - FunctionsErrorCode.INVALID_ARGUMENT: - _HttpErrorCode(_CanonicalErrorCodeName.INVALID_ARGUMENT, 400), - FunctionsErrorCode.DEADLINE_EXCEEDED: - _HttpErrorCode(_CanonicalErrorCodeName.DEADLINE_EXCEEDED, 504), - FunctionsErrorCode.NOT_FOUND: - _HttpErrorCode(_CanonicalErrorCodeName.NOT_FOUND, 404), - FunctionsErrorCode.ALREADY_EXISTS: - _HttpErrorCode(_CanonicalErrorCodeName.ALREADY_EXISTS, 409), - FunctionsErrorCode.PERMISSION_DENIED: - _HttpErrorCode(_CanonicalErrorCodeName.PERMISSION_DENIED, 403), - FunctionsErrorCode.UNAUTHENTICATED: - _HttpErrorCode(_CanonicalErrorCodeName.UNAUTHENTICATED, 401), - FunctionsErrorCode.RESOURCE_EXHAUSTED: - _HttpErrorCode(_CanonicalErrorCodeName.RESOURCE_EXHAUSTED, 429), - FunctionsErrorCode.FAILED_PRECONDITION: - _HttpErrorCode(_CanonicalErrorCodeName.FAILED_PRECONDITION, 400), - FunctionsErrorCode.ABORTED: - _HttpErrorCode(_CanonicalErrorCodeName.ABORTED, 409), - FunctionsErrorCode.OUT_OF_RANGE: - _HttpErrorCode(_CanonicalErrorCodeName.OUT_OF_RANGE, 400), - FunctionsErrorCode.UNIMPLEMENTED: - _HttpErrorCode(_CanonicalErrorCodeName.UNIMPLEMENTED, 501), - FunctionsErrorCode.INTERNAL: - _HttpErrorCode(_CanonicalErrorCodeName.INTERNAL, 500), - FunctionsErrorCode.UNAVAILABLE: - _HttpErrorCode(_CanonicalErrorCodeName.UNAVAILABLE, 503), - FunctionsErrorCode.DATA_LOSS: - _HttpErrorCode(_CanonicalErrorCodeName.DATA_LOSS, 500), + FunctionsErrorCode.OK: _HttpErrorCode(_CanonicalErrorCodeName.OK, 200), + FunctionsErrorCode.CANCELLED: _HttpErrorCode(_CanonicalErrorCodeName.CANCELLED, 499), + FunctionsErrorCode.UNKNOWN: _HttpErrorCode(_CanonicalErrorCodeName.UNKNOWN, 500), + FunctionsErrorCode.INVALID_ARGUMENT: _HttpErrorCode( + _CanonicalErrorCodeName.INVALID_ARGUMENT, 400 + ), + FunctionsErrorCode.DEADLINE_EXCEEDED: _HttpErrorCode( + _CanonicalErrorCodeName.DEADLINE_EXCEEDED, 504 + ), + FunctionsErrorCode.NOT_FOUND: _HttpErrorCode(_CanonicalErrorCodeName.NOT_FOUND, 404), + FunctionsErrorCode.ALREADY_EXISTS: _HttpErrorCode(_CanonicalErrorCodeName.ALREADY_EXISTS, 409), + FunctionsErrorCode.PERMISSION_DENIED: _HttpErrorCode( + _CanonicalErrorCodeName.PERMISSION_DENIED, 403 + ), + FunctionsErrorCode.UNAUTHENTICATED: _HttpErrorCode( + _CanonicalErrorCodeName.UNAUTHENTICATED, 401 + ), + FunctionsErrorCode.RESOURCE_EXHAUSTED: _HttpErrorCode( + _CanonicalErrorCodeName.RESOURCE_EXHAUSTED, 429 + ), + FunctionsErrorCode.FAILED_PRECONDITION: _HttpErrorCode( + _CanonicalErrorCodeName.FAILED_PRECONDITION, 400 + ), + FunctionsErrorCode.ABORTED: _HttpErrorCode(_CanonicalErrorCodeName.ABORTED, 409), + FunctionsErrorCode.OUT_OF_RANGE: _HttpErrorCode(_CanonicalErrorCodeName.OUT_OF_RANGE, 400), + FunctionsErrorCode.UNIMPLEMENTED: _HttpErrorCode(_CanonicalErrorCodeName.UNIMPLEMENTED, 501), + FunctionsErrorCode.INTERNAL: _HttpErrorCode(_CanonicalErrorCodeName.INTERNAL, 500), + FunctionsErrorCode.UNAVAILABLE: _HttpErrorCode(_CanonicalErrorCodeName.UNAVAILABLE, 503), + FunctionsErrorCode.DATA_LOSS: _HttpErrorCode(_CanonicalErrorCodeName.DATA_LOSS, 500), } """ Standard error codes and HTTP statuses for different ways a request can fail, @@ -352,8 +348,7 @@ class CallableRequest(_typing.Generic[_core.T]): _C2 = _typing.Callable[[CallableRequest[_typing.Any]], _typing.Any] -def _on_call_handler(func: _C2, request: Request, - enforce_app_check: bool) -> Response: +def _on_call_handler(func: _C2, request: Request, enforce_app_check: bool) -> Response: try: if not _util.valid_on_call_request(request): _logging.error("Invalid request, unable to process.") @@ -366,27 +361,26 @@ def _on_call_handler(func: _C2, request: Request, token_status = _util.on_call_check_tokens(request) if token_status.auth == _util.OnCallTokenState.INVALID: - raise HttpsError(FunctionsErrorCode.UNAUTHENTICATED, - "Unauthenticated") + raise HttpsError(FunctionsErrorCode.UNAUTHENTICATED, "Unauthenticated") if enforce_app_check and token_status.app in ( - _util.OnCallTokenState.MISSING, _util.OnCallTokenState.INVALID): - raise HttpsError(FunctionsErrorCode.UNAUTHENTICATED, - "Unauthenticated") + _util.OnCallTokenState.MISSING, + _util.OnCallTokenState.INVALID, + ): + raise HttpsError(FunctionsErrorCode.UNAUTHENTICATED, "Unauthenticated") if token_status.app == _util.OnCallTokenState.VALID and token_status.app_token is not None: context = _dataclasses.replace( context, - app=AppCheckData(token_status.app_token["sub"], - token_status.app_token), + app=AppCheckData(token_status.app_token["sub"], token_status.app_token), ) if token_status.auth_token is not None: context = _dataclasses.replace( context, auth=AuthData( - token_status.auth_token["uid"] - if "uid" in token_status.auth_token else None, - token_status.auth_token), + token_status.auth_token["uid"] if "uid" in token_status.auth_token else None, + token_status.auth_token, + ), ) instance_id = request.headers.get("Firebase-Instance-ID-Token") @@ -397,8 +391,7 @@ def _on_call_handler(func: _C2, request: Request, # pushes with FCM. In that case, the FCM APIs will validate the token. context = _dataclasses.replace( context, - instance_id_token=request.headers.get( - "Firebase-Instance-ID-Token"), + instance_id_token=request.headers.get("Firebase-Instance-ID-Token"), ) result = _core._with_init(func)(context) return _jsonify(result=result) @@ -436,7 +429,6 @@ def example(request: Request) -> Response: options = HttpsOptions(**kwargs) def on_request_inner_decorator(func: _C1): - @_functools.wraps(func) def on_request_wrapped(request: Request) -> Response: if options.cors is not None: diff --git a/src/firebase_functions/identity_fn.py b/src/firebase_functions/identity_fn.py index dfe4e2a..1cf78c3 100644 --- a/src/firebase_functions/identity_fn.py +++ b/src/firebase_functions/identity_fn.py @@ -33,6 +33,7 @@ class AuthUserInfo: """ User info that is part of the AuthUserRecord. """ + uid: str """The user identifier for the linked provider.""" @@ -57,6 +58,7 @@ class AuthUserMetadata: """ Additional metadata about the user. """ + creation_time: _dt.datetime """The date the user was created.""" @@ -348,14 +350,12 @@ class BeforeSignInResponse(BeforeCreateResponse, total=False): """The user's session claims object if available.""" -BeforeUserCreatedCallable = _typing.Callable[[AuthBlockingEvent], - BeforeCreateResponse | None] +BeforeUserCreatedCallable = _typing.Callable[[AuthBlockingEvent], BeforeCreateResponse | None] """ The type of the callable for 'before_user_created' blocking events. """ -BeforeUserSignedInCallable = _typing.Callable[[AuthBlockingEvent], - BeforeSignInResponse | None] +BeforeUserSignedInCallable = _typing.Callable[[AuthBlockingEvent], BeforeSignInResponse | None] """ The type of the callable for 'before_user_signed_in' blocking events. """ @@ -393,6 +393,7 @@ def before_user_signed_in_decorator(func: BeforeUserSignedInCallable): @_functools.wraps(func) def before_user_signed_in_wrapped(request: _Request) -> _Response: from firebase_functions.private._identity_fn import before_operation_handler + return before_operation_handler( func, event_type_before_sign_in, @@ -447,6 +448,7 @@ def before_user_created_decorator(func: BeforeUserCreatedCallable): @_functools.wraps(func) def before_user_created_wrapped(request: _Request) -> _Response: from firebase_functions.private._identity_fn import before_operation_handler + return before_operation_handler( func, event_type_before_create, diff --git a/src/firebase_functions/logger.py b/src/firebase_functions/logger.py index 62562bb..aae0f6a 100644 --- a/src/firebase_functions/logger.py +++ b/src/firebase_functions/logger.py @@ -49,10 +49,14 @@ def _entry_from_args(severity: LogSeverity, *args, **kwargs) -> LogEntry: Creates a `LogEntry` from the given arguments. """ - message: str = " ".join([ - value if isinstance(value, str) else _json.dumps( - _remove_circular(value), ensure_ascii=False) for value in args - ]) + message: str = " ".join( + [ + value + if isinstance(value, str) + else _json.dumps(_remove_circular(value), ensure_ascii=False) + for value in args + ] + ) other: _typing.Dict[str, _typing.Any] = { key: value if isinstance(value, str) else _remove_circular(value) @@ -66,8 +70,7 @@ def _entry_from_args(severity: LogSeverity, *args, **kwargs) -> LogEntry: return _typing.cast(LogEntry, entry) -def _remove_circular(obj: _typing.Any, - refs: _typing.Set[_typing.Any] | None = None): +def _remove_circular(obj: _typing.Any, refs: _typing.Set[_typing.Any] | None = None): """ Removes circular references from the given object and replaces them with "[CIRCULAR]". """ @@ -86,9 +89,7 @@ def _remove_circular(obj: _typing.Any, # Recursively process the object based on its type result: _typing.Any if isinstance(obj, dict): - result = { - key: _remove_circular(value, refs) for key, value in obj.items() - } + result = {key: _remove_circular(value, refs) for key, value in obj.items()} elif isinstance(obj, list): result = [_remove_circular(item, refs) for item in obj] elif isinstance(obj, tuple): @@ -111,8 +112,7 @@ def _get_write_file(severity: LogSeverity) -> _typing.TextIO: def write(entry: LogEntry) -> None: write_file = _get_write_file(entry["severity"]) - print(_json.dumps(_remove_circular(entry), ensure_ascii=False), - file=write_file) + print(_json.dumps(_remove_circular(entry), ensure_ascii=False), file=write_file) def debug(*args, **kwargs) -> None: diff --git a/src/firebase_functions/options.py b/src/firebase_functions/options.py index 2f7db7d..76be849 100644 --- a/src/firebase_functions/options.py +++ b/src/firebase_functions/options.py @@ -15,6 +15,7 @@ Module for options that can be used to configure Cloud Functions deployments. """ + # pylint: disable=protected-access import enum as _enum import dataclasses as _dataclasses @@ -31,7 +32,8 @@ """An alias of the zoneinfo.ZoneInfo for convenience.""" RESET_VALUE = _util.Sentinel( - "Special configuration value to reset configuration to platform default.") + "Special configuration value to reset configuration to platform default." +) """Special configuration value to reset configuration to platform default.""" @@ -134,19 +136,18 @@ def __str__(self) -> str: @_dataclasses.dataclass(frozen=True) -class RateLimits(): +class RateLimits: """ How congestion control should be applied to the function. """ - max_concurrent_dispatches: int | Expression[ - int] | _util.Sentinel | None = None + + max_concurrent_dispatches: int | Expression[int] | _util.Sentinel | None = None """ The maximum number of requests that can be outstanding at a time. If left unspecified, defaults to 1000. """ - max_dispatches_per_second: int | Expression[ - int] | _util.Sentinel | None = None + max_dispatches_per_second: int | Expression[int] | _util.Sentinel | None = None """ The maximum number of requests that can be invoked per second. If left unspecified, defaults to 500. @@ -154,7 +155,7 @@ class RateLimits(): @_dataclasses.dataclass(frozen=True) -class RetryConfig(): +class RetryConfig: """ How a task should be retried in the event of a non-2xx return. """ @@ -352,8 +353,7 @@ def convert_secret(secret) -> str: secret_value = secret.name return secret_value - merged_options["secrets"] = list( - map(convert_secret, _typing.cast(list, self.secrets))) + merged_options["secrets"] = list(map(convert_secret, _typing.cast(list, self.secrets))) # _util.Sentinel values are converted to `None` in ManifestEndpoint generation # after other None values are removed - so as to keep them in the generated # YAML output as 'null' values. @@ -363,17 +363,14 @@ def _endpoint(self, **kwargs) -> _manifest.ManifestEndpoint: assert kwargs["func_name"] is not None options_dict = self._asdict_with_global_options() options = self.__class__(**options_dict) - secret_envs: list[ - _manifest.SecretEnvironmentVariable] | _util.Sentinel = [] + secret_envs: list[_manifest.SecretEnvironmentVariable] | _util.Sentinel = [] if options.secrets is not None: if isinstance(options.secrets, list): - def convert_secret( - secret) -> _manifest.SecretEnvironmentVariable: + def convert_secret(secret) -> _manifest.SecretEnvironmentVariable: return {"key": secret} - secret_envs = list( - map(convert_secret, _typing.cast(list, options.secrets))) + secret_envs = list(map(convert_secret, _typing.cast(list, options.secrets))) elif options.secrets is _util.Sentinel: secret_envs = _typing.cast(_util.Sentinel, options.secrets) @@ -385,16 +382,16 @@ def convert_secret( vpc: _manifest.VpcSettings | None = None if isinstance(options.vpc_connector, str): - vpc = ({ - "connector": - options.vpc_connector, - "egressSettings": - options.vpc_connector_egress_settings.value if isinstance( - options.vpc_connector_egress_settings, VpcEgressSetting) - else options.vpc_connector_egress_settings - } if options.vpc_connector_egress_settings is not None else { - "connector": options.vpc_connector - }) + vpc = ( + { + "connector": options.vpc_connector, + "egressSettings": options.vpc_connector_egress_settings.value + if isinstance(options.vpc_connector_egress_settings, VpcEgressSetting) + else options.vpc_connector_egress_settings, + } + if options.vpc_connector_egress_settings is not None + else {"connector": options.vpc_connector} + ) endpoint = _manifest.ManifestEndpoint( entryPoint=kwargs["func_name"], @@ -445,29 +442,35 @@ def _endpoint( self, **kwargs, ) -> _manifest.ManifestEndpoint: - rate_limits: _manifest.RateLimits | None = _manifest.RateLimits( - maxConcurrentDispatches=self.rate_limits.max_concurrent_dispatches, - maxDispatchesPerSecond=self.rate_limits.max_dispatches_per_second, - ) if self.rate_limits is not None else None - - retry_config: _manifest.RetryConfigTasks | None = _manifest.RetryConfigTasks( - maxAttempts=self.retry_config.max_attempts, - maxRetrySeconds=self.retry_config.max_retry_seconds, - maxBackoffSeconds=self.retry_config.max_backoff_seconds, - maxDoublings=self.retry_config.max_doublings, - minBackoffSeconds=self.retry_config.min_backoff_seconds, - ) if self.retry_config is not None else None + rate_limits: _manifest.RateLimits | None = ( + _manifest.RateLimits( + maxConcurrentDispatches=self.rate_limits.max_concurrent_dispatches, + maxDispatchesPerSecond=self.rate_limits.max_dispatches_per_second, + ) + if self.rate_limits is not None + else None + ) + + retry_config: _manifest.RetryConfigTasks | None = ( + _manifest.RetryConfigTasks( + maxAttempts=self.retry_config.max_attempts, + maxRetrySeconds=self.retry_config.max_retry_seconds, + maxBackoffSeconds=self.retry_config.max_backoff_seconds, + maxDoublings=self.retry_config.max_doublings, + minBackoffSeconds=self.retry_config.min_backoff_seconds, + ) + if self.retry_config is not None + else None + ) kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "taskQueueTrigger": - _manifest.TaskQueueTrigger( - rateLimits=rate_limits, - retryConfig=retry_config, - ), + "taskQueueTrigger": _manifest.TaskQueueTrigger( + rateLimits=rate_limits, + retryConfig=retry_config, + ), } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) def _required_apis(self) -> list[_manifest.ManifestRequiredApi]: return [ @@ -506,11 +509,9 @@ def _endpoint( kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "eventTrigger": - event_trigger, + "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -533,10 +534,14 @@ def _endpoint( "topic": self.topic, } event_type = "google.cloud.pubsub.topic.v1.messagePublished" - return _manifest.ManifestEndpoint(**_typing.cast( - _typing.Dict, - _dataclasses.asdict(super()._endpoint( - **kwargs, event_filters=event_filters, event_type=event_type)))) + return _manifest.ManifestEndpoint( + **_typing.cast( + _typing.Dict, + _dataclasses.asdict( + super()._endpoint(**kwargs, event_filters=event_filters, event_type=event_type) + ), + ) + ) class AlertType(str, _enum.Enum): @@ -633,13 +638,18 @@ def _endpoint( event_filters["appid"] = self.app_id event_type = "google.firebase.firebasealerts.alerts.v1.published" - return _manifest.ManifestEndpoint(**_typing.cast( - _typing.Dict, - _dataclasses.asdict(super()._endpoint( - **kwargs, - event_filters=event_filters, - event_type=event_type, - )))) + return _manifest.ManifestEndpoint( + **_typing.cast( + _typing.Dict, + _dataclasses.asdict( + super()._endpoint( + **kwargs, + event_filters=event_filters, + event_type=event_type, + ) + ), + ) + ) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -724,7 +734,8 @@ def _endpoint( ) -> _manifest.ManifestEndpoint: assert kwargs["alert_type"] is not None return FirebaseAlertOptions( - alert_type=kwargs["alert_type"],)._endpoint(**kwargs) + alert_type=kwargs["alert_type"], + )._endpoint(**kwargs) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -765,16 +776,22 @@ def _endpoint( **kwargs, ) -> _manifest.ManifestEndpoint: event_filters = {} if self.filters is None else self.filters - endpoint = _manifest.ManifestEndpoint(**_typing.cast( - _typing.Dict, - _dataclasses.asdict(super()._endpoint( - **kwargs, - event_filters=event_filters, - event_type=self.event_type, - )))) + endpoint = _manifest.ManifestEndpoint( + **_typing.cast( + _typing.Dict, + _dataclasses.asdict( + super()._endpoint( + **kwargs, + event_filters=event_filters, + event_type=self.event_type, + ) + ), + ) + ) assert endpoint.eventTrigger is not None - channel = (self.channel if self.channel is not None else - "locations/us-central1/channels/firebase") + channel = ( + self.channel if self.channel is not None else "locations/us-central1/channels/firebase" + ) endpoint.eventTrigger["channel"] = channel return endpoint @@ -848,15 +865,13 @@ def _endpoint( kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "scheduleTrigger": - _manifest.ScheduleTrigger( - schedule=self.schedule, - timeZone=time_zone, - retryConfig=retry_config, - ), + "scheduleTrigger": _manifest.ScheduleTrigger( + schedule=self.schedule, + timeZone=time_zone, + retryConfig=retry_config, + ), } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) def _required_apis(self) -> list[_manifest.ManifestRequiredApi]: return [ @@ -893,7 +908,8 @@ def _endpoint( raise ValueError( "Missing bucket name. If you are unit testing, please specify a bucket name" " by providing a bucket name directly to the event handler or by setting the" - " FIREBASE_CONFIG environment variable.") + " FIREBASE_CONFIG environment variable." + ) event_filters: _typing.Any = { "bucket": bucket, } @@ -905,11 +921,9 @@ def _endpoint( kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "eventTrigger": - event_trigger, + "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -961,11 +975,9 @@ def _endpoint( kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "eventTrigger": - event_trigger, + "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -1000,20 +1012,16 @@ def _endpoint( eventType=kwargs["event_type"], options=_manifest.BlockingTriggerOptions( idToken=self.id_token if self.id_token is not None else False, - accessToken=self.access_token - if self.access_token is not None else False, - refreshToken=self.refresh_token - if self.refresh_token is not None else False, + accessToken=self.access_token if self.access_token is not None else False, + refreshToken=self.refresh_token if self.refresh_token is not None else False, ), ) kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "blockingTrigger": - blocking_trigger, + "blockingTrigger": blocking_trigger, } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) def _required_apis(self) -> list[_manifest.ManifestRequiredApi]: return [ @@ -1057,10 +1065,8 @@ def _endpoint( document_pattern: _path_pattern.PathPattern = kwargs["document_pattern"] event_filter_document = document_pattern.value event_filters: _typing.Any = { - "database": - self.database if self.database is not None else "(default)", - "namespace": - self.namespace if self.namespace is not None else "(default)", + "database": self.database if self.database is not None else "(default)", + "namespace": self.namespace if self.namespace is not None else "(default)", } event_filters_path_patterns: _typing.Any = {} if document_pattern.has_wildcards: @@ -1076,11 +1082,9 @@ def _endpoint( kwargs_merged = { **_dataclasses.asdict(super()._endpoint(**kwargs)), - "eventTrigger": - event_trigger, + "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -1090,8 +1094,7 @@ class HttpsOptions(RuntimeOptions): Internal use only. """ - invoker: str | list[str] | _typing.Literal["public", - "private"] | None = None + invoker: str | list[str] | _typing.Literal["public", "private"] | None = None """ Invoker to set access control on HTTP functions. """ @@ -1132,9 +1135,9 @@ def _endpoint( invoker = self.invoker if isinstance(invoker, str): invoker = [invoker] - assert len( - invoker - ) >= 1, "HttpsOptions: Invalid option for invoker - must be a non-empty list." + assert len(invoker) >= 1, ( + "HttpsOptions: Invalid option for invoker - must be a non-empty list." + ) assert "" not in invoker, ( "HttpsOptions: Invalid option for invoker - must be a non-empty string." ) @@ -1146,8 +1149,7 @@ def _endpoint( https_trigger["invoker"] = invoker kwargs_merged["httpsTrigger"] = https_trigger - return _manifest.ManifestEndpoint( - **_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) _GLOBAL_OPTIONS = RuntimeOptions() diff --git a/src/firebase_functions/params.py b/src/firebase_functions/params.py index 4aa7406..6284c08 100644 --- a/src/firebase_functions/params.py +++ b/src/firebase_functions/params.py @@ -47,13 +47,11 @@ def value(self) -> _T: def _obj_cel_name(obj: _T) -> _T: - return obj if not isinstance(obj, Expression) else object.__getattribute__( - obj, "_cel_") + return obj if not isinstance(obj, Expression) else object.__getattribute__(obj, "_cel_") def _quote_if_string(literal: _T) -> _T: - return _obj_cel_name(literal) if not isinstance(literal, - str) else f'"{literal}"' + return _obj_cel_name(literal) if not isinstance(literal, str) else f'"{literal}"' _params: dict[str, Expression] = {} @@ -65,6 +63,7 @@ class TernaryExpression(Expression[_T], _typing.Generic[_T]): A CEL expression that evaluates to one of two values based on the value of another expression. """ + test: Expression[bool] if_true: _T if_false: _T @@ -87,6 +86,7 @@ class CompareExpression(Expression[bool], _typing.Generic[_T]): A CEL expression that evaluates to boolean true or false based on a comparison between the value of another expression and a literal of that same type. """ + comparator: str left: Expression[_T] right: _T @@ -141,7 +141,7 @@ class SelectInput(_typing.Generic[_T]): @_dataclasses.dataclass(frozen=True) -class MultiSelectInput(): +class MultiSelectInput: """ Specifies that a Param's value should be determined by having the user select a subset from a list of pre-canned options interactively at deploy-time. @@ -179,6 +179,7 @@ class TextInput: class ResourceType(str, _enum.Enum): """The type of resource that a picker should pick.""" + STORAGE_BUCKET = "storage.googleapis.com/Bucket" def __str__(self) -> str: @@ -231,8 +232,7 @@ class Param(Expression[_T]): deployments. """ - input: TextInput | ResourceInput | SelectInput[ - _T] | MultiSelectInput | None = None + input: TextInput | ResourceInput | SelectInput[_T] | MultiSelectInput | None = None """ The type of input that is required for this param, e.g. TextInput. """ @@ -254,7 +254,8 @@ def __post_init__(self): if not _re.match(r"^[A-Z0-9_]+$", self.name): raise ValueError( "Parameter names must only use uppercase letters, numbers and " - "underscores, e.g. 'UPPER_SNAKE_CASE'.") + "underscores, e.g. 'UPPER_SNAKE_CASE'." + ) if self.name in _params: raise ValueError( f"Duplicate Parameter Error: The parameter '{self.name}' has already been declared." @@ -294,7 +295,8 @@ def __post_init__(self): if not _re.match(r"^[A-Z0-9_]+$", self.name): raise ValueError( "Parameter names must only use uppercase letters, numbers and " - "underscores, e.g. 'UPPER_SNAKE_CASE'.") + "underscores, e.g. 'UPPER_SNAKE_CASE'." + ) if self.name in _params: raise ValueError( f"Duplicate Parameter Error: The parameter '{self.name}' has already been declared." @@ -323,8 +325,7 @@ def value(self) -> str: return _os.environ[self.name] if self.default is not None: - return self.default.value if isinstance( - self.default, Expression) else self.default + return self.default.value if isinstance(self.default, Expression) else self.default return str() @@ -338,8 +339,7 @@ def value(self) -> int: if _os.environ.get(self.name) is not None: return int(_os.environ[self.name]) if self.default is not None: - return self.default.value if isinstance( - self.default, Expression) else self.default + return self.default.value if isinstance(self.default, Expression) else self.default return int() @@ -356,8 +356,7 @@ def value(self) -> float: if _os.environ.get(self.name) is not None: return float(_os.environ[self.name]) if self.default is not None: - return self.default.value if isinstance( - self.default, Expression) else self.default + return self.default.value if isinstance(self.default, Expression) else self.default return float() @@ -371,8 +370,7 @@ def value(self) -> bool: if env_value is not None: return env_value.lower() == "true" if self.default is not None: - return self.default.value if isinstance( - self.default, Expression) else self.default + return self.default.value if isinstance(self.default, Expression) else self.default return False @@ -386,8 +384,7 @@ def value(self) -> list[str]: # If the environment variable starts with "[" and ends with "]", # then assume it is a JSON array and try to parse it. # (This is for Cloud Run (v2 Functions), the environment variable is a JSON array.) - if _os.environ[self.name].startswith("[") and _os.environ[ - self.name].endswith("]"): + if _os.environ[self.name].startswith("[") and _os.environ[self.name].endswith("]"): try: return _json.loads(_os.environ[self.name]) except _json.JSONDecodeError: @@ -397,8 +394,7 @@ def value(self) -> list[str]: # variable is a comma-separated list.) return list(filter(len, _os.environ[self.name].split(","))) if self.default is not None: - return self.default.value if isinstance( - self.default, Expression) else self.default + return self.default.value if isinstance(self.default, Expression) else self.default return [] diff --git a/src/firebase_functions/private/_alerts_fn.py b/src/firebase_functions/private/_alerts_fn.py index bd4484a..b13a698 100644 --- a/src/firebase_functions/private/_alerts_fn.py +++ b/src/firebase_functions/private/_alerts_fn.py @@ -24,6 +24,7 @@ def plan_update_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.billing_fn import PlanUpdatePayload + return PlanUpdatePayload( notification_type=payload["notificationType"], billing_plan=payload["billingPlan"], @@ -33,6 +34,7 @@ def plan_update_payload_from_ce_payload(payload: dict): def plan_automated_update_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.billing_fn import PlanAutomatedUpdatePayload + return PlanAutomatedUpdatePayload( notification_type=payload["notificationType"], billing_plan=payload["billingPlan"], @@ -41,6 +43,7 @@ def plan_automated_update_payload_from_ce_payload(payload: dict): def in_app_feedback_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.app_distribution_fn import InAppFeedbackPayload + return InAppFeedbackPayload( feedback_report=payload["feedbackReport"], feedback_console_uri=payload["feedbackConsoleUri"], @@ -54,6 +57,7 @@ def in_app_feedback_payload_from_ce_payload(payload: dict): def new_tester_device_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.app_distribution_fn import NewTesterDevicePayload + return NewTesterDevicePayload( tester_name=payload["testerName"], tester_email=payload["testerEmail"], @@ -64,6 +68,7 @@ def new_tester_device_payload_from_ce_payload(payload: dict): def threshold_alert_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.performance_fn import ThresholdAlertPayload + return ThresholdAlertPayload( event_name=payload["eventName"], event_type=payload["eventType"], @@ -81,6 +86,7 @@ def threshold_alert_payload_from_ce_payload(payload: dict): def issue_from_ce_payload(payload: dict): from firebase_functions.alerts.crashlytics_fn import Issue + return Issue( id=payload["id"], title=payload["title"], @@ -91,21 +97,24 @@ def issue_from_ce_payload(payload: dict): def new_fatal_issue_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.crashlytics_fn import NewFatalIssuePayload + return NewFatalIssuePayload(issue=issue_from_ce_payload(payload["issue"])) def new_nonfatal_issue_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.crashlytics_fn import NewNonfatalIssuePayload - return NewNonfatalIssuePayload( - issue=issue_from_ce_payload(payload["issue"])) + + return NewNonfatalIssuePayload(issue=issue_from_ce_payload(payload["issue"])) def regression_alert_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.crashlytics_fn import RegressionAlertPayload - return RegressionAlertPayload(type=payload["type"], - issue=issue_from_ce_payload(payload["issue"]), - resolve_time=_util.timestamp_conversion( - payload["resolveTime"])) + + return RegressionAlertPayload( + type=payload["type"], + issue=issue_from_ce_payload(payload["issue"]), + resolve_time=_util.timestamp_conversion(payload["resolveTime"]), + ) def trending_issue_details_from_ce_payload(payload: dict): @@ -125,8 +134,7 @@ def stability_digest_payload_from_ce_payload(payload: dict): return StabilityDigestPayload( digest_date=_util.timestamp_conversion(payload["digestDate"]), trending_issues=[ - trending_issue_details_from_ce_payload(issue) - for issue in payload["trendingIssues"] + trending_issue_details_from_ce_payload(issue) for issue in payload["trendingIssues"] ], ) @@ -149,7 +157,9 @@ def new_anr_issue_payload_from_ce_payload(payload: dict): return NewAnrIssuePayload(issue=issue_from_ce_payload(payload["issue"])) -def firebase_alert_data_from_ce(event_dict: dict,) -> FirebaseAlertData: +def firebase_alert_data_from_ce( + event_dict: dict, +) -> FirebaseAlertData: from firebase_functions.options import AlertType alert_type: str = event_dict["alerttype"] @@ -157,8 +167,7 @@ def firebase_alert_data_from_ce(event_dict: dict,) -> FirebaseAlertData: if alert_type == AlertType.CRASHLYTICS_NEW_FATAL_ISSUE.value: alert_payload = new_fatal_issue_payload_from_ce_payload(alert_payload) elif alert_type == AlertType.CRASHLYTICS_NEW_NONFATAL_ISSUE.value: - alert_payload = new_nonfatal_issue_payload_from_ce_payload( - alert_payload) + alert_payload = new_nonfatal_issue_payload_from_ce_payload(alert_payload) elif alert_type == AlertType.CRASHLYTICS_REGRESSION.value: alert_payload = regression_alert_payload_from_ce_payload(alert_payload) elif alert_type == AlertType.CRASHLYTICS_STABILITY_DIGEST.value: @@ -170,8 +179,7 @@ def firebase_alert_data_from_ce(event_dict: dict,) -> FirebaseAlertData: elif alert_type == AlertType.BILLING_PLAN_UPDATE.value: alert_payload = plan_update_payload_from_ce_payload(alert_payload) elif alert_type == AlertType.BILLING_PLAN_AUTOMATED_UPDATE.value: - alert_payload = plan_automated_update_payload_from_ce_payload( - alert_payload) + alert_payload = plan_automated_update_payload_from_ce_payload(alert_payload) elif alert_type == AlertType.APP_DISTRIBUTION_NEW_TESTER_IOS_DEVICE.value: alert_payload = new_tester_device_payload_from_ce_payload(alert_payload) elif alert_type == AlertType.APP_DISTRIBUTION_IN_APP_FEEDBACK.value: @@ -184,7 +192,8 @@ def firebase_alert_data_from_ce(event_dict: dict,) -> FirebaseAlertData: return FirebaseAlertData( create_time=_util.timestamp_conversion(event_dict["createTime"]), end_time=_util.timestamp_conversion(event_dict["endTime"]) - if "endTime" in event_dict else None, + if "endTime" in event_dict + else None, payload=alert_payload, ) diff --git a/src/firebase_functions/private/_identity_fn.py b/src/firebase_functions/private/_identity_fn.py index f13d150..f91ee84 100644 --- a/src/firebase_functions/private/_identity_fn.py +++ b/src/firebase_functions/private/_identity_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Cloud functions to handle Eventarc events.""" + # pylint: disable=protected-access import typing as _typing import datetime as _dt @@ -53,6 +54,7 @@ def _auth_user_info_from_token_data(token_data: dict[str, _typing.Any]): from firebase_functions.identity_fn import AuthUserInfo + return AuthUserInfo( uid=token_data["uid"], provider_id=token_data["provider_id"], @@ -65,24 +67,24 @@ def _auth_user_info_from_token_data(token_data: dict[str, _typing.Any]): def _auth_user_metadata_from_token_data(token_data: dict[str, _typing.Any]): from firebase_functions.identity_fn import AuthUserMetadata - creation_time = _dt.datetime.utcfromtimestamp( - int(token_data["creation_time"]) / 1000.0) + + creation_time = _dt.datetime.utcfromtimestamp(int(token_data["creation_time"]) / 1000.0) last_sign_in_time = None if "last_sign_in_time" in token_data: last_sign_in_time = _dt.datetime.utcfromtimestamp( - int(token_data["last_sign_in_time"]) / 1000.0) + int(token_data["last_sign_in_time"]) / 1000.0 + ) - return AuthUserMetadata(creation_time=creation_time, - last_sign_in_time=last_sign_in_time) + return AuthUserMetadata(creation_time=creation_time, last_sign_in_time=last_sign_in_time) def _auth_multi_factor_info_from_token_data(token_data: dict[str, _typing.Any]): from firebase_functions.identity_fn import AuthMultiFactorInfo + enrollment_time = token_data.get("enrollment_time") if enrollment_time: enrollment_time = _dt.datetime.fromisoformat(enrollment_time) - factor_id = token_data["factor_id"] if not token_data.get( - "phone_number") else "phone" + factor_id = token_data["factor_id"] if not token_data.get("phone_number") else "phone" return AuthMultiFactorInfo( uid=token_data["uid"], factor_id=factor_id, @@ -92,8 +94,7 @@ def _auth_multi_factor_info_from_token_data(token_data: dict[str, _typing.Any]): ) -def _auth_multi_factor_settings_from_token_data(token_data: dict[str, - _typing.Any]): +def _auth_multi_factor_settings_from_token_data(token_data: dict[str, _typing.Any]): if not token_data: return None @@ -112,6 +113,7 @@ def _auth_multi_factor_settings_from_token_data(token_data: dict[str, def _auth_user_record_from_token_data(token_data: dict[str, _typing.Any]): from firebase_functions.identity_fn import AuthUserRecord + return AuthUserRecord( uid=token_data["uid"], email=token_data.get("email"), @@ -122,24 +124,24 @@ def _auth_user_record_from_token_data(token_data: dict[str, _typing.Any]): disabled=token_data.get("disabled", False), metadata=_auth_user_metadata_from_token_data(token_data["metadata"]), provider_data=[ - _auth_user_info_from_token_data(info) - for info in token_data["provider_data"] + _auth_user_info_from_token_data(info) for info in token_data["provider_data"] ], password_hash=token_data.get("password_hash"), password_salt=token_data.get("password_salt"), custom_claims=token_data.get("custom_claims"), tenant_id=token_data.get("tenant_id"), - tokens_valid_after_time=_dt.datetime.utcfromtimestamp( - token_data["tokens_valid_after_time"]) - if token_data.get("tokens_valid_after_time") else None, - multi_factor=_auth_multi_factor_settings_from_token_data( - token_data["multi_factor"]) - if "multi_factor" in token_data else None, + tokens_valid_after_time=_dt.datetime.utcfromtimestamp(token_data["tokens_valid_after_time"]) + if token_data.get("tokens_valid_after_time") + else None, + multi_factor=_auth_multi_factor_settings_from_token_data(token_data["multi_factor"]) + if "multi_factor" in token_data + else None, ) def _additional_user_info_from_token_data(token_data: dict[str, _typing.Any]): from firebase_functions.identity_fn import AdditionalUserInfo + raw_user_info = token_data.get("raw_user_info") profile = None username = None @@ -155,9 +157,11 @@ def _additional_user_info_from_token_data(token_data: dict[str, _typing.Any]): elif sign_in_method == "twitter.com": username = profile.get("screen_name") - provider_id: str = ("password" - if token_data.get("sign_in_method") == "emailLink" else - str(token_data.get("sign_in_method"))) + provider_id: str = ( + "password" + if token_data.get("sign_in_method") == "emailLink" + else str(token_data.get("sign_in_method")) + ) is_new_user = token_data.get("event_type") == "beforeCreate" @@ -170,23 +174,27 @@ def _additional_user_info_from_token_data(token_data: dict[str, _typing.Any]): ) -def _credential_from_token_data(token_data: dict[str, _typing.Any], - time: float): - if (not token_data.get("sign_in_attributes") and - not token_data.get("oauth_id_token") and - not token_data.get("oauth_access_token") and - not token_data.get("oauth_refresh_token")): +def _credential_from_token_data(token_data: dict[str, _typing.Any], time: float): + if ( + not token_data.get("sign_in_attributes") + and not token_data.get("oauth_id_token") + and not token_data.get("oauth_access_token") + and not token_data.get("oauth_refresh_token") + ): return None from firebase_functions.identity_fn import Credential oauth_expires_in = token_data.get("oauth_expires_in") - expiration_time = (_dt.datetime.utcfromtimestamp(time + oauth_expires_in) - if oauth_expires_in else None) + expiration_time = ( + _dt.datetime.utcfromtimestamp(time + oauth_expires_in) if oauth_expires_in else None + ) - provider_id: str = ("password" - if token_data.get("sign_in_method") == "emailLink" else - str(token_data.get("sign_in_method"))) + provider_id: str = ( + "password" + if token_data.get("sign_in_method") == "emailLink" + else str(token_data.get("sign_in_method")) + ) return Credential( claims=token_data.get("sign_in_attributes"), @@ -200,9 +208,9 @@ def _credential_from_token_data(token_data: dict[str, _typing.Any], ) -def _auth_blocking_event_from_token_data(event_type: str, - token_data: dict[str, _typing.Any]): +def _auth_blocking_event_from_token_data(event_type: str, token_data: dict[str, _typing.Any]): from firebase_functions.identity_fn import AuthBlockingEvent + return AuthBlockingEvent( data=_auth_user_record_from_token_data(token_data["user_record"]), locale=token_data.get("locale"), @@ -229,10 +237,8 @@ def _validate_auth_response( if auth_response is None: auth_response = {} - custom_claims: dict[str, - _typing.Any] | None = auth_response.get("custom_claims") - session_claims: dict[str, _typing.Any] | None = auth_response.get( - "session_claims") + custom_claims: dict[str, _typing.Any] | None = auth_response.get("custom_claims") + session_claims: dict[str, _typing.Any] | None = auth_response.get("session_claims") if session_claims and event_type == event_type_before_create: raise HttpsError( @@ -242,10 +248,7 @@ def _validate_auth_response( ) if custom_claims: - invalid_claims = [ - claim for claim in _disallowed_custom_claims - if claim in custom_claims - ] + invalid_claims = [claim for claim in _disallowed_custom_claims if claim in custom_claims] if invalid_claims: raise HttpsError( @@ -262,11 +265,7 @@ def _validate_auth_response( ) if event_type == event_type_before_sign_in and session_claims: - - invalid_claims = [ - claim for claim in _disallowed_custom_claims - if claim in session_claims - ] + invalid_claims = [claim for claim in _disallowed_custom_claims if claim in session_claims] if invalid_claims: raise HttpsError( @@ -282,10 +281,7 @@ def _validate_auth_response( f"{_claims_max_payload_size} characters.", ) - combined_claims = { - **(custom_claims if custom_claims else {}), - **session_claims - } + combined_claims = {**(custom_claims if custom_claims else {}), **session_claims} if len(_json.dumps(combined_claims)) > _claims_max_payload_size: raise HttpsError( @@ -309,27 +305,23 @@ def _validate_auth_response( if "session_claims" in auth_response_keys: auth_response_dict["sessionClaims"] = auth_response["session_claims"] if "recaptcha_action_override" in auth_response_keys: - auth_response_dict["recaptchaActionOverride"] = auth_response[ - "recaptcha_action_override"] + auth_response_dict["recaptchaActionOverride"] = auth_response["recaptcha_action_override"] return auth_response_dict def _generate_response_payload( - auth_response_dict: dict[str, _typing.Any] | None + auth_response_dict: dict[str, _typing.Any] | None, ) -> dict[str, _typing.Any]: if not auth_response_dict: return {} formatted_auth_response = auth_response_dict.copy() - recaptcha_action_override = formatted_auth_response.pop( - "recaptchaActionOverride", None) + recaptcha_action_override = formatted_auth_response.pop("recaptchaActionOverride", None) result = {} update_mask = ",".join(formatted_auth_response.keys()) if len(update_mask) != 0: - result["userRecord"] = { - **formatted_auth_response, "updateMask": update_mask - } + result["userRecord"] = {**formatted_auth_response, "updateMask": update_mask} if recaptcha_action_override is not None: result["recaptchaActionOverride"] = recaptcha_action_override @@ -343,6 +335,7 @@ def before_operation_handler( request: _Request, ) -> _Response: from firebase_functions.identity_fn import BeforeCreateResponse, BeforeSignInResponse + try: if not _util.valid_on_call_request(request): _logging.error("Invalid request, unable to process.") @@ -356,8 +349,7 @@ def before_operation_handler( jwt_token = request.json["data"]["jwt"] decoded_token = _token_verifier.verify_auth_blocking_token(jwt_token) event = _auth_blocking_event_from_token_data(event_type, decoded_token) - auth_response: BeforeCreateResponse | BeforeSignInResponse | None = _with_init( - func)(event) + auth_response: BeforeCreateResponse | BeforeSignInResponse | None = _with_init(func)(event) if not auth_response: return _jsonify({}) auth_response_dict = _validate_auth_response(event_type, auth_response) diff --git a/src/firebase_functions/private/manifest.py b/src/firebase_functions/private/manifest.py index 7ebeac2..74fdb33 100644 --- a/src/firebase_functions/private/manifest.py +++ b/src/firebase_functions/private/manifest.py @@ -55,52 +55,57 @@ class EventTrigger(_typing.TypedDict): Trigger definitions for endpoints that listen to CloudEvents emitted by other systems (or legacy Google events for GCF gen 1) """ - eventFilters: _typing_extensions.NotRequired[dict[str, str | - _params.Expression[str]]] - eventFilterPathPatterns: _typing_extensions.NotRequired[dict[ - str, str | _params.Expression[str]]] + + eventFilters: _typing_extensions.NotRequired[dict[str, str | _params.Expression[str]]] + eventFilterPathPatterns: _typing_extensions.NotRequired[ + dict[str, str | _params.Expression[str]] + ] channel: _typing_extensions.NotRequired[str] eventType: _typing_extensions.Required[str] - retry: _typing_extensions.Required[bool | _params.Expression[bool] | - _util.Sentinel] + retry: _typing_extensions.Required[bool | _params.Expression[bool] | _util.Sentinel] class RetryConfigBase(_typing.TypedDict): """ Retry configuration for a endpoint. """ - maxRetrySeconds: _typing_extensions.NotRequired[int | - _params.Expression[int] | - _util.Sentinel | None] - maxBackoffSeconds: _typing_extensions.NotRequired[int | - _params.Expression[int] | - _util.Sentinel | None] - maxDoublings: _typing_extensions.NotRequired[int | _params.Expression[int] | - _util.Sentinel | None] - minBackoffSeconds: _typing_extensions.NotRequired[int | - _params.Expression[int] | - _util.Sentinel | None] + + maxRetrySeconds: _typing_extensions.NotRequired[ + int | _params.Expression[int] | _util.Sentinel | None + ] + maxBackoffSeconds: _typing_extensions.NotRequired[ + int | _params.Expression[int] | _util.Sentinel | None + ] + maxDoublings: _typing_extensions.NotRequired[ + int | _params.Expression[int] | _util.Sentinel | None + ] + minBackoffSeconds: _typing_extensions.NotRequired[ + int | _params.Expression[int] | _util.Sentinel | None + ] class RetryConfigTasks(RetryConfigBase): """ Retry configuration for a task. """ - maxAttempts: _typing_extensions.NotRequired[int | _params.Expression[int] | - _util.Sentinel | None] + + maxAttempts: _typing_extensions.NotRequired[ + int | _params.Expression[int] | _util.Sentinel | None + ] class RetryConfigScheduler(RetryConfigBase): """ Retry configuration for a schedule. """ - retryCount: _typing_extensions.NotRequired[int | _params.Expression[int] | - _util.Sentinel | None] + + retryCount: _typing_extensions.NotRequired[ + int | _params.Expression[int] | _util.Sentinel | None + ] class RateLimits(_typing.TypedDict): - maxConcurrentDispatches: int | _params.Expression[ - int] | _util.Sentinel | None + maxConcurrentDispatches: int | _params.Expression[int] | _util.Sentinel | None maxDispatchesPerSecond: int | _params.Expression[int] | _util.Sentinel | None @@ -110,6 +115,7 @@ class TaskQueueTrigger(_typing.TypedDict): Trigger definitions for RPCs servers using the HTTP protocol defined at https://firebase.google.com/docs/functions/callable-reference """ + retryConfig: RetryConfigTasks | None rateLimits: RateLimits | None @@ -143,8 +149,7 @@ class ManifestEndpoint: entryPoint: str | None = None region: list[str] | None = _dataclasses.field(default_factory=list[str]) platform: str | None = "gcfv2" - availableMemoryMb: int | _params.Expression[ - int] | _util.Sentinel | None = None + availableMemoryMb: int | _params.Expression[int] | _util.Sentinel | None = None maxInstances: int | _params.Expression[int] | _util.Sentinel | None = None minInstances: int | _params.Expression[int] | _util.Sentinel | None = None concurrency: int | _params.Expression[int] | _util.Sentinel | None = None @@ -154,9 +159,9 @@ class ManifestEndpoint: vpc: VpcSettings | None = None labels: dict[str, str] | None = None ingressSettings: str | None | _util.Sentinel = None - secretEnvironmentVariables: list[ - SecretEnvironmentVariable] | _util.Sentinel | None = _dataclasses.field( - default_factory=list[SecretEnvironmentVariable]) + secretEnvironmentVariables: list[SecretEnvironmentVariable] | _util.Sentinel | None = ( + _dataclasses.field(default_factory=list[SecretEnvironmentVariable]) + ) httpsTrigger: HttpsTrigger | None = None callableTrigger: CallableTrigger | None = None eventTrigger: EventTrigger | None = None @@ -174,27 +179,28 @@ class ManifestRequiredApi(_typing.TypedDict): class ManifestStack: endpoints: dict[str, ManifestEndpoint] specVersion: str = "v1alpha1" - params: list[_typing.Any] | None = _dataclasses.field( - default_factory=list[_typing.Any]) + params: list[_typing.Any] | None = _dataclasses.field(default_factory=list[_typing.Any]) requiredAPIs: list[ManifestRequiredApi] = _dataclasses.field( - default_factory=list[ManifestRequiredApi]) + default_factory=list[ManifestRequiredApi] + ) def _param_input_to_spec( - param_input: _params.TextInput | _params.ResourceInput | - _params.SelectInput | _params.MultiSelectInput + param_input: _params.TextInput + | _params.ResourceInput + | _params.SelectInput + | _params.MultiSelectInput, ) -> dict[str, _typing.Any]: if isinstance(param_input, _params.TextInput): return { "text": { - key: value for key, value in { - "example": - param_input.example, - "validationRegex": - param_input.validation_regex, - "validationErrorMessage": - param_input.validation_error_message, - }.items() if value is not None + key: value + for key, value in { + "example": param_input.example, + "validationRegex": param_input.validation_regex, + "validationErrorMessage": param_input.validation_error_message, + }.items() + if value is not None } } @@ -206,24 +212,27 @@ def _param_input_to_spec( } if isinstance(param_input, (_params.MultiSelectInput, _params.SelectInput)): - key = "select" if isinstance(param_input, - _params.SelectInput) else "multiSelect" + key = "select" if isinstance(param_input, _params.SelectInput) else "multiSelect" return { key: { - "options": [{ - key: value for key, value in { - "value": option.value, - "label": option.label, - }.items() if value is not None - } for option in param_input.options], + "options": [ + { + key: value + for key, value in { + "value": option.value, + "label": option.label, + }.items() + if value is not None + } + for option in param_input.options + ], }, } return {} -def _param_to_spec( - param: _params.Param | _params.SecretParam) -> dict[str, _typing.Any]: +def _param_to_spec(param: _params.Param | _params.SecretParam) -> dict[str, _typing.Any]: spec_dict: dict[str, _typing.Any] = { "name": param.name, "label": param.label, @@ -232,8 +241,9 @@ def _param_to_spec( } if isinstance(param, _params.Param): - spec_dict["default"] = f"{param.default}" if isinstance( - param.default, _params.Expression) else param.default + spec_dict["default"] = ( + f"{param.default}" if isinstance(param.default, _params.Expression) else param.default + ) if param.input: spec_dict["input"] = _param_input_to_spec(param.input) diff --git a/src/firebase_functions/private/path_pattern.py b/src/firebase_functions/private/path_pattern.py index 6bd36c3..8391772 100644 --- a/src/firebase_functions/private/path_pattern.py +++ b/src/firebase_functions/private/path_pattern.py @@ -30,7 +30,7 @@ def join_path(base: str, child: str) -> str: def trim_param(param: str) -> str: param_no_braces = param[1:-1] if "=" in param_no_braces: - return param_no_braces[:param_no_braces.index("=")] + return param_no_braces[: param_no_braces.index("=")] return param_no_braces @@ -50,6 +50,7 @@ class PathSegment: """ A segment of a path pattern. """ + name: SegmentName value: str trimmed: str @@ -89,6 +90,7 @@ class SingleCaptureSegment(PathSegment): """ A segment of a path pattern that captures a single segment. """ + name = SegmentName.SINGLE_CAPTURE def __init__(self, value): @@ -129,6 +131,7 @@ class PathPattern: Implements Eventarc's path pattern from the spec https://cloud.google.com/eventarc/docs/path-patterns """ + segments: list[PathSegment] def __init__(self, raw_path: str): @@ -157,15 +160,17 @@ def value(self) -> str: @property def has_wildcards(self) -> bool: - return any(segment.is_single_segment_wildcard or - segment.is_multi_segment_wildcard - for segment in self.segments) + return any( + segment.is_single_segment_wildcard or segment.is_multi_segment_wildcard + for segment in self.segments + ) @property def has_captures(self) -> bool: - return any(segment.name in (SegmentName.SINGLE_CAPTURE, - SegmentName.MULTI_CAPTURE) - for segment in self.segments) + return any( + segment.name in (SegmentName.SINGLE_CAPTURE, SegmentName.MULTI_CAPTURE) + for segment in self.segments + ) def extract_matches(self, path: str) -> dict[str, str]: matches: dict[str, str] = {} @@ -180,7 +185,6 @@ def extract_matches(self, path: str) -> dict[str, str]: if segment.name == SegmentName.SINGLE_CAPTURE: matches[segment.trimmed] = path_segments[path_ndx] elif segment.name == SegmentName.MULTI_CAPTURE: - matches[segment.trimmed] = "/".join( - path_segments[path_ndx:next_path_ndx]) + matches[segment.trimmed] = "/".join(path_segments[path_ndx:next_path_ndx]) path_ndx = next_path_ndx if segment.is_multi_segment_wildcard else path_ndx + 1 return matches diff --git a/src/firebase_functions/private/serving.py b/src/firebase_functions/private/serving.py index 1cfde33..e943910 100644 --- a/src/firebase_functions/private/serving.py +++ b/src/firebase_functions/private/serving.py @@ -14,6 +14,7 @@ """ Module used to serve Firebase functions locally and remotely. """ + # pylint: disable=protected-access import os import inspect @@ -52,7 +53,6 @@ def get_functions(): def to_spec(data: dict) -> dict: - def convert_value(obj): if isinstance(obj, enum.Enum): return obj.value @@ -62,13 +62,12 @@ def convert_value(obj): return list(map(convert_value, obj)) return obj - without_nones = dict( - (k, convert_value(v)) for k, v in data.items() if v is not None) + without_nones = dict((k, convert_value(v)) for k, v in data.items() if v is not None) return without_nones def merge_required_apis( - required_apis: list[_manifest.ManifestRequiredApi] + required_apis: list[_manifest.ManifestRequiredApi], ) -> list[_manifest.ManifestRequiredApi]: api_to_reasons: dict[str, list[str]] = {} for api_reason in required_apis: @@ -125,7 +124,6 @@ def get_functions_yaml() -> Response: def quitquitquit(): - def quit_after_close(): kill(getpid(), SIGTERM) diff --git a/src/firebase_functions/private/token_verifier.py b/src/firebase_functions/private/token_verifier.py index a986ec4..9eeb725 100644 --- a/src/firebase_functions/private/token_verifier.py +++ b/src/firebase_functions/private/token_verifier.py @@ -14,7 +14,16 @@ """ Module for internal token verification. """ -from firebase_admin import _token_gen, exceptions, _auth_utils, initialize_app, get_app, _apps, _DEFAULT_APP_NAME + +from firebase_admin import ( + _token_gen, + exceptions, + _auth_utils, + initialize_app, + get_app, + _apps, + _DEFAULT_APP_NAME, +) from google.auth import jwt import google.auth.exceptions import google.oauth2.id_token @@ -31,100 +40,123 @@ class _JWTVerifier: """Verifies Firebase JWTs (ID tokens or session cookies).""" def __init__(self, **kwargs): - self.project_id = kwargs.pop('project_id') - self.short_name = kwargs.pop('short_name') - self.operation = kwargs.pop('operation') - self.url = kwargs.pop('doc_url') - self.cert_url = kwargs.pop('cert_url') - self.issuer = kwargs.pop('issuer') - self.expected_audience = kwargs.pop('expected_audience') - if self.short_name[0].lower() in 'aeiou': - self.articled_short_name = 'an {0}'.format(self.short_name) + self.project_id = kwargs.pop("project_id") + self.short_name = kwargs.pop("short_name") + self.operation = kwargs.pop("operation") + self.url = kwargs.pop("doc_url") + self.cert_url = kwargs.pop("cert_url") + self.issuer = kwargs.pop("issuer") + self.expected_audience = kwargs.pop("expected_audience") + if self.short_name[0].lower() in "aeiou": + self.articled_short_name = "an {0}".format(self.short_name) else: - self.articled_short_name = 'a {0}'.format(self.short_name) - self._invalid_token_error = kwargs.pop('invalid_token_error') - self._expired_token_error = kwargs.pop('expired_token_error') + self.articled_short_name = "a {0}".format(self.short_name) + self._invalid_token_error = kwargs.pop("invalid_token_error") + self._expired_token_error = kwargs.pop("expired_token_error") def verify(self, token, request): """Verifies the signature and data for the provided JWT.""" - token = token.encode('utf-8') if isinstance(token, str) else token + token = token.encode("utf-8") if isinstance(token, str) else token if not isinstance(token, bytes) or not token: raise ValueError( - 'Illegal {0} provided: {1}. {0} must be a non-empty ' - 'string.'.format(self.short_name, token)) + "Illegal {0} provided: {1}. {0} must be a non-empty string.".format( + self.short_name, token + ) + ) if not self.project_id: raise ValueError( - 'Failed to ascertain project ID from the credential or the environment. Project ' - 'ID is required to call {0}. Initialize the app with a credentials.Certificate ' - 'or set your Firebase project ID as an app option. Alternatively set the ' - 'GOOGLE_CLOUD_PROJECT environment variable.'.format( - self.operation)) + "Failed to ascertain project ID from the credential or the environment. Project " + "ID is required to call {0}. Initialize the app with a credentials.Certificate " + "or set your Firebase project ID as an app option. Alternatively set the " + "GOOGLE_CLOUD_PROJECT environment variable.".format(self.operation) + ) header, payload = self._decode_unverified(token) - issuer = payload.get('iss') - audience = payload.get('aud') - subject = payload.get('sub') + issuer = payload.get("iss") + audience = payload.get("aud") + subject = payload.get("sub") expected_issuer = self.issuer + self.project_id project_id_match_msg = ( - 'Make sure the {0} comes from the same Firebase project as the service account used ' - 'to authenticate this SDK.'.format(self.short_name)) - verify_id_token_msg = ( - 'See {0} for details on how to retrieve {1}.'.format( - self.url, self.short_name)) + "Make sure the {0} comes from the same Firebase project as the service account used " + "to authenticate this SDK.".format(self.short_name) + ) + verify_id_token_msg = "See {0} for details on how to retrieve {1}.".format( + self.url, self.short_name + ) emulated = _auth_utils.is_emulated() error_message = None if audience == _token_gen.FIREBASE_AUDIENCE: - error_message = ('{0} expects {1}, but was given a custom ' - 'token.'.format(self.operation, - self.articled_short_name)) - elif not emulated and not header.get('kid'): - if header.get('alg') == 'HS256' and payload.get( - 'v') == 0 and 'uid' in payload.get('d', {}): - error_message = ( - '{0} expects {1}, but was given a legacy custom ' - 'token.'.format(self.operation, self.articled_short_name)) + error_message = "{0} expects {1}, but was given a custom token.".format( + self.operation, self.articled_short_name + ) + elif not emulated and not header.get("kid"): + if ( + header.get("alg") == "HS256" + and payload.get("v") == 0 + and "uid" in payload.get("d", {}) + ): + error_message = "{0} expects {1}, but was given a legacy custom token.".format( + self.operation, self.articled_short_name + ) else: - error_message = 'Firebase {0} has no "kid" claim.'.format( - self.short_name) - elif not emulated and header.get('alg') != 'RS256': + error_message = 'Firebase {0} has no "kid" claim.'.format(self.short_name) + elif not emulated and header.get("alg") != "RS256": error_message = ( - 'Firebase {0} has incorrect algorithm. Expected "RS256" but got ' - '"{1}". {2}'.format(self.short_name, header.get('alg'), - verify_id_token_msg)) + 'Firebase {0} has incorrect algorithm. Expected "RS256" but got "{1}". {2}'.format( + self.short_name, header.get("alg"), verify_id_token_msg + ) + ) elif not emulated and self.expected_audience and self.expected_audience not in audience: error_message = ( 'Firebase {0} has incorrect "aud" (audience) claim. Expected "{1}" but ' - 'got "{2}". {3} {4}'.format(self.short_name, - self.expected_audience, audience, - project_id_match_msg, - verify_id_token_msg)) + 'got "{2}". {3} {4}'.format( + self.short_name, + self.expected_audience, + audience, + project_id_match_msg, + verify_id_token_msg, + ) + ) elif not emulated and not self.expected_audience and audience != self.project_id: error_message = ( 'Firebase {0} has incorrect "aud" (audience) claim. Expected "{1}" but ' - 'got "{2}". {3} {4}'.format(self.short_name, self.project_id, - audience, project_id_match_msg, - verify_id_token_msg)) + 'got "{2}". {3} {4}'.format( + self.short_name, + self.project_id, + audience, + project_id_match_msg, + verify_id_token_msg, + ) + ) elif issuer != expected_issuer: error_message = ( 'Firebase {0} has incorrect "iss" (issuer) claim. Expected "{1}" but ' - 'got "{2}". {3} {4}'.format(self.short_name, expected_issuer, - issuer, project_id_match_msg, - verify_id_token_msg)) + 'got "{2}". {3} {4}'.format( + self.short_name, + expected_issuer, + issuer, + project_id_match_msg, + verify_id_token_msg, + ) + ) elif subject is None or not isinstance(subject, str): - error_message = ('Firebase {0} has no "sub" (subject) claim. ' - '{1}'.format(self.short_name, verify_id_token_msg)) + error_message = 'Firebase {0} has no "sub" (subject) claim. {1}'.format( + self.short_name, verify_id_token_msg + ) elif not subject: - error_message = ( - 'Firebase {0} has an empty string "sub" (subject) claim. ' - '{1}'.format(self.short_name, verify_id_token_msg)) + error_message = 'Firebase {0} has an empty string "sub" (subject) claim. {1}'.format( + self.short_name, verify_id_token_msg + ) elif len(subject) > 128: error_message = ( - 'Firebase {0} has a "sub" (subject) claim longer than 128 characters. ' - '{1}'.format(self.short_name, verify_id_token_msg)) + 'Firebase {0} has a "sub" (subject) claim longer than 128 characters. {1}'.format( + self.short_name, verify_id_token_msg + ) + ) if error_message: raise self._invalid_token_error(error_message) @@ -138,15 +170,15 @@ def verify(self, token, request): request=request, # If expected_audience is set then we have already verified # the audience above. - audience=(None - if self.expected_audience else self.project_id), - certs_url=self.cert_url) - verified_claims['uid'] = verified_claims['sub'] + audience=(None if self.expected_audience else self.project_id), + certs_url=self.cert_url, + ) + verified_claims["uid"] = verified_claims["sub"] return verified_claims except google.auth.exceptions.TransportError as error: raise _token_gen.CertificateFetchError(str(error), cause=error) except ValueError as error: - if 'Token expired' in str(error): + if "Token expired" in str(error): raise self._expired_token_error(str(error), cause=error) raise self._invalid_token_error(str(error), cause=error) @@ -162,11 +194,10 @@ def _decode_unverified(self, token): class InvalidAuthBlockingTokenError(exceptions.InvalidArgumentError): """The provided auth blocking token is not a token.""" - default_message = 'The provided auth blocking token is invalid' + default_message = "The provided auth blocking token is invalid" def __init__(self, message, cause=None, http_response=None): - exceptions.InvalidArgumentError.__init__(self, message, cause, - http_response) + exceptions.InvalidArgumentError.__init__(self, message, cause, http_response) class ExpiredAuthBlockingTokenError(InvalidAuthBlockingTokenError): @@ -183,15 +214,14 @@ def __init__(self, app): super().__init__(app) self.auth_blocking_token_verifier = _JWTVerifier( project_id=app.project_id, - short_name='Auth Blocking token', - operation='verify_auth_blocking_token()', - doc_url= - 'https://cloud.google.com/identity-platform/docs/blocking-functions', + short_name="Auth Blocking token", + operation="verify_auth_blocking_token()", + doc_url="https://cloud.google.com/identity-platform/docs/blocking-functions", cert_url=_token_gen.ID_TOKEN_CERT_URI, issuer=_token_gen.ID_TOKEN_ISSUER_PREFIX, invalid_token_error=InvalidAuthBlockingTokenError, expired_token_error=ExpiredAuthBlockingTokenError, - expected_audience='run.app', # v2 only + expected_audience="run.app", # v2 only ) def verify_auth_blocking_token(self, auth_blocking_token): @@ -205,5 +235,4 @@ def verify_auth_blocking_token(auth_blocking_token): """Verifies the provided auth blocking token.""" if _DEFAULT_APP_NAME not in _apps: initialize_app() - return AuthBlockingTokenVerifier( - get_app()).verify_auth_blocking_token(auth_blocking_token) + return AuthBlockingTokenVerifier(get_app()).verify_auth_blocking_token(auth_blocking_token) diff --git a/src/firebase_functions/private/util.py b/src/firebase_functions/private/util.py index b24ea26..8c7dc5c 100644 --- a/src/firebase_functions/private/util.py +++ b/src/firebase_functions/private/util.py @@ -31,8 +31,7 @@ P = _typing.ParamSpec("P") R = _typing.TypeVar("R") -JWT_REGEX = _re.compile( - r"^[a-zA-Z0-9\-_=]+?\.[a-zA-Z0-9\-_=]+?\.([a-zA-Z0-9\-_=]+)?$") +JWT_REGEX = _re.compile(r"^[a-zA-Z0-9\-_=]+?\.[a-zA-Z0-9\-_=]+?\.([a-zA-Z0-9\-_=]+)?$") class Sentinel: @@ -42,14 +41,12 @@ def __init__(self, description): self.description = description def __eq__(self, other): - return isinstance(other, - Sentinel) and self.description == other.description + return isinstance(other, Sentinel) and self.description == other.description def copy_func_kwargs( func_with_kwargs: _typing.Callable[P, _typing.Any], # pylint: disable=unused-argument ) -> _typing.Callable[[_typing.Callable[..., R]], _typing.Callable[P, R]]: - def return_func(func: _typing.Callable[..., R]) -> _typing.Callable[P, R]: return _typing.cast(_typing.Callable[P, R], func) @@ -95,16 +92,16 @@ def deep_merge(dict1, dict2): def valid_on_call_request(request: _Request) -> bool: """Validate request""" - if (_on_call_valid_method(request) and - _on_call_valid_content_type(request) and - _on_call_valid_body(request)): + if ( + _on_call_valid_method(request) + and _on_call_valid_content_type(request) + and _on_call_valid_body(request) + ): return True return False -def convert_keys_to_camel_case( - data: dict[str, _typing.Any]) -> dict[str, _typing.Any]: - +def convert_keys_to_camel_case(data: dict[str, _typing.Any]) -> dict[str, _typing.Any]: def snake_to_camel(word: str) -> str: components = word.split("_") return components[0] + "".join(x.capitalize() for x in components[1:]) @@ -123,9 +120,7 @@ def _on_call_valid_body(request: _Request) -> bool: _logging.warning("Request body is missing data.", request.json) return False - extra_keys = { - key: request.json[key] for key in request.json.keys() if key != "data" - } + extra_keys = {key: request.json[key] for key in request.json.keys() if key != "data"} if len(extra_keys) != 0: _logging.warning( "Request body has extra fields: %s", @@ -212,11 +207,11 @@ def as_dict(self) -> dict: def _on_call_check_auth_token( - request: _Request + request: _Request, ) -> None | _typing.Literal[OnCallTokenState.INVALID] | dict[str, _typing.Any]: """ - Validates the auth token in a callable request. - If verify_token is False, the token will be decoded without verification. + Validates the auth token in a callable request. + If verify_token is False, the token will be decoded without verification. """ authorization = request.headers.get("Authorization") if authorization is None: @@ -235,7 +230,7 @@ def _on_call_check_auth_token( def _on_call_check_app_token( - request: _Request + request: _Request, ) -> None | _typing.Literal[OnCallTokenState.INVALID] | dict[str, _typing.Any]: """Validates the app token in a callable request.""" app_check = request.headers.get("X-Firebase-AppCheck") @@ -304,14 +299,13 @@ def on_call_check_tokens(request: _Request) -> _OnCallTokenVerification: if len(errs) == 0: _logging.info("Callable request verification passed: %s", log_payload) else: - _logging.warning(f"Callable request verification failed: ${errs}", - log_payload) + _logging.warning(f"Callable request verification failed: ${errs}", log_payload) return verifications @_dataclasses.dataclass(frozen=True) -class FirebaseConfig(): +class FirebaseConfig: """ A collection of configuration options needed to initialize a firebase App. @@ -340,8 +334,7 @@ def firebase_config() -> None | FirebaseConfig: with open(config_file, "r", encoding="utf8") as json_file: json_str = json_file.read() except Exception as err: - raise ValueError( - f"Unable to read file {config_file}. {err}") from err + raise ValueError(f"Unable to read file {config_file}. {err}") from err try: json_data: dict = _json.loads(json_str) except Exception as err: @@ -355,13 +348,11 @@ def nanoseconds_timestamp_conversion(time: str) -> _dt.datetime: """Converts a nanosecond timestamp and returns a datetime object of the current time in UTC""" # Separate the date and time part from the nanoseconds. - datetime_str, nanosecond_str = time.replace("Z", "").replace("z", - "").split(".") + datetime_str, nanosecond_str = time.replace("Z", "").replace("z", "").split(".") # Parse the date and time part of the string. event_time = _dt.datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S") # Add the microseconds and timezone. - event_time = event_time.replace(microsecond=int(nanosecond_str[:6]), - tzinfo=_dt.timezone.utc) + event_time = event_time.replace(microsecond=int(nanosecond_str[:6]), tzinfo=_dt.timezone.utc) return event_time @@ -398,8 +389,7 @@ def get_precision_timestamp(time: str) -> PrecisionTimestamp: return PrecisionTimestamp.SECONDS # Split the fraction from the timezone specifier ('Z' or 'z') - s_fraction, _ = s_fraction.split( - "Z") if "Z" in s_fraction else s_fraction.split("z") + s_fraction, _ = s_fraction.split("Z") if "Z" in s_fraction else s_fraction.split("z") # If the fraction is more than 6 digits long, it's a nanosecond timestamp if len(s_fraction) > 6: diff --git a/src/firebase_functions/pubsub_fn.py b/src/firebase_functions/pubsub_fn.py index ebac90e..3b2df27 100644 --- a/src/firebase_functions/pubsub_fn.py +++ b/src/firebase_functions/pubsub_fn.py @@ -14,6 +14,7 @@ """ Functions to handle events from Google Cloud Pub/Sub. """ + # pylint: disable=protected-access import dataclasses as _dataclasses import datetime as _dt @@ -68,9 +69,7 @@ def json(self) -> T | None: else: return None except Exception as error: - raise ValueError( - f"Unable to parse Pub/Sub message data as JSON: {error}" - ) from error + raise ValueError(f"Unable to parse Pub/Sub message data as JSON: {error}") from error @_dataclasses.dataclass(frozen=True) @@ -80,6 +79,7 @@ class MessagePublishedData(_typing.Generic[T]): 'T' Type representing `Message.data`'s JSON format. """ + message: Message[T] """ Google Cloud Pub/Sub message. @@ -109,8 +109,7 @@ def _message_handler( if "." not in event_dict["time"]: event_dict["time"] = event_dict["time"].replace("Z", ".000000Z") if "." not in message_dict["publish_time"]: - message_dict["publish_time"] = message_dict["publish_time"].replace( - "Z", ".000000Z") + message_dict["publish_time"] = message_dict["publish_time"].replace("Z", ".000000Z") time = _dt.datetime.strptime( event_dict["time"], @@ -185,7 +184,6 @@ def example(event: CloudEvent[MessagePublishedData[object]]) -> None: options = PubSubOptions(**kwargs) def on_message_published_inner_decorator(func: _C1): - @_functools.wraps(func) def on_message_published_wrapped(raw: _ce.CloudEvent): return _message_handler(func, raw) diff --git a/src/firebase_functions/remote_config_fn.py b/src/firebase_functions/remote_config_fn.py index c48436d..cdb0d17 100644 --- a/src/firebase_functions/remote_config_fn.py +++ b/src/firebase_functions/remote_config_fn.py @@ -15,6 +15,7 @@ """ Cloud functions to handle Remote Config events. """ + import dataclasses as _dataclasses import functools as _functools import datetime as _dt @@ -163,8 +164,7 @@ def _config_handler(func: _C1, raw: _ce.CloudEvent) -> None: config_data = ConfigUpdateData( version_number=event_data["versionNumber"], - update_time=_dt.datetime.strptime(event_data["updateTime"], - "%Y-%m-%dT%H:%M:%S.%f%z"), + update_time=_dt.datetime.strptime(event_data["updateTime"], "%Y-%m-%dT%H:%M:%S.%f%z"), update_user=ConfigUser( name=event_data["updateUser"]["name"], email=event_data["updateUser"]["email"], @@ -216,7 +216,6 @@ def example(event: CloudEvent[ConfigUpdateData]) -> None: options = EventHandlerOptions(**kwargs) def on_config_updated_inner_decorator(func: _C1): - @_functools.wraps(func) def on_config_updated_wrapped(raw: _ce.CloudEvent): return _config_handler(func, raw) @@ -226,7 +225,7 @@ def on_config_updated_wrapped(raw: _ce.CloudEvent): options._endpoint( func_name=func.__name__, event_filters={}, - event_type="google.firebase.remoteconfig.remoteConfig.v1.updated" + event_type="google.firebase.remoteconfig.remoteConfig.v1.updated", ), ) return on_config_updated_wrapped diff --git a/src/firebase_functions/scheduler_fn.py b/src/firebase_functions/scheduler_fn.py index c5a92c9..44f4a55 100644 --- a/src/firebase_functions/scheduler_fn.py +++ b/src/firebase_functions/scheduler_fn.py @@ -28,6 +28,7 @@ ) from firebase_functions.core import _with_init + # Export for user convenience. # pylint: disable=unused-import from firebase_functions.options import Timezone @@ -91,12 +92,10 @@ def example(event: scheduler_fn.ScheduledEvent) -> None: options = _options.ScheduleOptions(**kwargs) def on_schedule_decorator(func: _C): - @_functools.wraps(func) def on_schedule_wrapped(request: _Request) -> _Response: schedule_time: _dt.datetime - schedule_time_str = request.headers.get( - "X-CloudScheduler-ScheduleTime") + schedule_time_str = request.headers.get("X-CloudScheduler-ScheduleTime") if schedule_time_str is None: schedule_time = _dt.datetime.utcnow() else: diff --git a/src/firebase_functions/storage_fn.py b/src/firebase_functions/storage_fn.py index 342d257..aa8b689 100644 --- a/src/firebase_functions/storage_fn.py +++ b/src/firebase_functions/storage_fn.py @@ -14,6 +14,7 @@ """ Functions to handle events from Google Cloud Storage. """ + # pylint: disable=protected-access import dataclasses as _dataclasses import datetime as _dt @@ -235,10 +236,11 @@ def _message_handler( updated=data.get("updated"), # Custom type fields: customer_encryption=CustomerEncryption( - encryption_algorithm=data["customerEncryption"] - ["encryptionAlgorithm"], + encryption_algorithm=data["customerEncryption"]["encryptionAlgorithm"], key_sha256=data["customerEncryption"]["keySha256"], - ) if data.get("customerEncryption") is not None else None, + ) + if data.get("customerEncryption") is not None + else None, ) event: CloudEvent[StorageObjectData] = CloudEvent( @@ -246,8 +248,7 @@ def _message_handler( id=event_attributes["id"], source=event_attributes["source"], specversion=event_attributes["specversion"], - subject=event_attributes["subject"] - if "subject" in event_attributes else None, + subject=event_attributes["subject"] if "subject" in event_attributes else None, time=_dt.datetime.strptime( event_attributes["time"], "%Y-%m-%dT%H:%M:%S.%f%z", @@ -284,15 +285,13 @@ def example(event: CloudEvent[StorageObjectData]) -> None: options = StorageOptions(**kwargs) def on_object_archived_inner_decorator(func: _C1): - @_functools.wraps(func) def on_object_archived_wrapped(raw: _ce.CloudEvent): return _message_handler(func, raw) _util.set_func_endpoint_attr( on_object_archived_wrapped, - options._endpoint(func_name=func.__name__, - event_type=_event_type_archived), + options._endpoint(func_name=func.__name__, event_type=_event_type_archived), ) return on_object_archived_wrapped @@ -326,15 +325,13 @@ def example(event: CloudEvent[StorageObjectData]) -> None: options = StorageOptions(**kwargs) def on_object_finalized_inner_decorator(func: _C1): - @_functools.wraps(func) def on_object_finalized_wrapped(raw: _ce.CloudEvent): return _message_handler(func, raw) _util.set_func_endpoint_attr( on_object_finalized_wrapped, - options._endpoint(func_name=func.__name__, - event_type=_event_type_finalized), + options._endpoint(func_name=func.__name__, event_type=_event_type_finalized), ) return on_object_finalized_wrapped @@ -369,15 +366,13 @@ def example(event: CloudEvent[StorageObjectData]) -> None: options = StorageOptions(**kwargs) def on_object_deleted_inner_decorator(func: _C1): - @_functools.wraps(func) def on_object_deleted_wrapped(raw: _ce.CloudEvent): return _message_handler(func, raw) _util.set_func_endpoint_attr( on_object_deleted_wrapped, - options._endpoint(func_name=func.__name__, - event_type=_event_type_deleted), + options._endpoint(func_name=func.__name__, event_type=_event_type_deleted), ) return on_object_deleted_wrapped @@ -408,15 +403,13 @@ def example(event: CloudEvent[StorageObjectData]) -> None: options = StorageOptions(**kwargs) def on_object_metadata_updated_inner_decorator(func: _C1): - @_functools.wraps(func) def on_object_metadata_updated_wrapped(raw: _ce.CloudEvent): return _message_handler(func, raw) _util.set_func_endpoint_attr( on_object_metadata_updated_wrapped, - options._endpoint(func_name=func.__name__, - event_type=_event_type_metadata_updated), + options._endpoint(func_name=func.__name__, event_type=_event_type_metadata_updated), ) return on_object_metadata_updated_wrapped diff --git a/src/firebase_functions/tasks_fn.py b/src/firebase_functions/tasks_fn.py index 7b8c675..c29763f 100644 --- a/src/firebase_functions/tasks_fn.py +++ b/src/firebase_functions/tasks_fn.py @@ -51,8 +51,7 @@ def _on_call_handler(func: _C2, request: Request) -> Response: # pushes with FCM. In that case, the FCM APIs will validate the token. context = _dataclasses.replace( context, - instance_id_token=request.headers.get( - "Firebase-Instance-ID-Token"), + instance_id_token=request.headers.get("Firebase-Instance-ID-Token"), ) result = _core._with_init(func)(context) return _jsonify(result=result) @@ -91,7 +90,6 @@ def example(request: tasks.CallableRequest) -> Any: options = _options.TaskQueueOptions(**kwargs) def on_task_dispatched_decorator(func: _C): - @_functools.wraps(func) def on_task_dispatched_wrapped(request: Request) -> Response: return _on_call_handler(func, request) diff --git a/src/firebase_functions/test_lab_fn.py b/src/firebase_functions/test_lab_fn.py index 7aede95..a3f55bb 100644 --- a/src/firebase_functions/test_lab_fn.py +++ b/src/firebase_functions/test_lab_fn.py @@ -15,6 +15,7 @@ """ Cloud functions to handle Test Lab events. """ + import dataclasses as _dataclasses import functools as _functools import datetime as _dt @@ -213,18 +214,15 @@ def _event_handler(func: _C1, raw: _ce.CloudEvent) -> None: event_dict = {**event_data, **event_attributes} test_lab_data = TestMatrixCompletedData( - create_time=_dt.datetime.strptime(event_data["createTime"], - "%Y-%m-%dT%H:%M:%S.%f%z"), + create_time=_dt.datetime.strptime(event_data["createTime"], "%Y-%m-%dT%H:%M:%S.%f%z"), state=TestState(event_data["state"]), invalid_matrix_details=event_data.get("invalidMatrixDetails"), outcome_summary=OutcomeSummary(event_data["outcomeSummary"]), result_storage=ResultStorage( - tool_results_history=event_data["resultStorage"] - ["toolResultsHistory"], + tool_results_history=event_data["resultStorage"]["toolResultsHistory"], results_uri=event_data["resultStorage"]["resultsUri"], gcs_path=event_data["resultStorage"]["gcsPath"], - tool_results_execution=event_data["resultStorage"].get( - "toolResultsExecution"), + tool_results_execution=event_data["resultStorage"].get("toolResultsExecution"), ), client_info=ClientInfo( client=event_data["clientInfo"]["client"], @@ -273,7 +271,6 @@ def example(event: CloudEvent[ConfigUpdateData]) -> None: options = EventHandlerOptions(**kwargs) def on_test_matrix_completed_inner_decorator(func: _C1): - @_functools.wraps(func) def on_test_matrix_completed_wrapped(raw: _ce.CloudEvent): return _event_handler(func, raw) @@ -283,7 +280,8 @@ def on_test_matrix_completed_wrapped(raw: _ce.CloudEvent): options._endpoint( func_name=func.__name__, event_filters={}, - event_type="google.firebase.testlab.testMatrix.v1.completed"), + event_type="google.firebase.testlab.testMatrix.v1.completed", + ), ) return on_test_matrix_completed_wrapped diff --git a/tests/test_db.py b/tests/test_db.py index 4e8b487..a0b6967 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -24,19 +24,21 @@ def init(): func = mock.Mock(__name__="example_func") decorated_func = db_fn.on_value_created(reference="path")(func) - event = CloudEvent(attributes={ - "specversion": "1.0", - "id": "id", - "source": "source", - "subject": "subject", - "type": "type", - "time": "2024-04-10T12:00:00.000Z", - "instance": "instance", - "ref": "ref", - "firebasedatabasehost": "firebasedatabasehost", - "location": "location", - }, - data={"delta": "delta"}) + event = CloudEvent( + attributes={ + "specversion": "1.0", + "id": "id", + "source": "source", + "subject": "subject", + "type": "type", + "time": "2024-04-10T12:00:00.000Z", + "instance": "instance", + "ref": "ref", + "firebasedatabasehost": "firebasedatabasehost", + "location": "location", + }, + data={"delta": "delta"}, + ) decorated_func(event) diff --git a/tests/test_eventarc_fn.py b/tests/test_eventarc_fn.py index 730812a..06a9d86 100644 --- a/tests/test_eventarc_fn.py +++ b/tests/test_eventarc_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Eventarc trigger function tests.""" + import unittest from unittest.mock import Mock diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py index fa4ed15..9a57a30 100644 --- a/tests/test_firestore_fn.py +++ b/tests/test_firestore_fn.py @@ -9,7 +9,7 @@ mocked_modules = { "google.cloud.firestore": MagicMock(), "google.cloud.firestore_v1": MagicMock(), - "firebase_admin": MagicMock() + "firebase_admin": MagicMock(), } @@ -21,47 +21,36 @@ class TestFirestore(TestCase): def test_firestore_endpoint_handler_calls_function_with_correct_args(self): with patch.dict("sys.modules", mocked_modules): from cloudevents.http import CloudEvent - from firebase_functions.firestore_fn import _event_type_created_with_auth_context as event_type, \ - _firestore_endpoint_handler as firestore_endpoint_handler, AuthEvent + from firebase_functions.firestore_fn import ( + _event_type_created_with_auth_context as event_type, + _firestore_endpoint_handler as firestore_endpoint_handler, + AuthEvent, + ) from firebase_functions.private import path_pattern func = Mock(__name__="example_func") document_pattern = path_pattern.PathPattern("foo/{bar}") attributes = { - "specversion": - "1.0", - "type": - event_type, - "source": - "https://example.com/testevent", - "time": - "2023-03-11T13:25:37.403Z", - "subject": - "test_subject", - "datacontenttype": - "application/json", - "location": - "projects/project-id/databases/(default)/documents/foo/{bar}", - "project": - "project-id", - "namespace": - "(default)", - "document": - "foo/{bar}", - "database": - "projects/project-id/databases/(default)", - "authtype": - "unauthenticated", - "authid": - "foo" + "specversion": "1.0", + "type": event_type, + "source": "https://example.com/testevent", + "time": "2023-03-11T13:25:37.403Z", + "subject": "test_subject", + "datacontenttype": "application/json", + "location": "projects/project-id/databases/(default)/documents/foo/{bar}", + "project": "project-id", + "namespace": "(default)", + "document": "foo/{bar}", + "database": "projects/project-id/databases/(default)", + "authtype": "unauthenticated", + "authid": "foo", } raw_event = CloudEvent(attributes=attributes, data=json.dumps({})) - firestore_endpoint_handler(func=func, - event_type=event_type, - document_pattern=document_pattern, - raw=raw_event) + firestore_endpoint_handler( + func=func, event_type=event_type, document_pattern=document_pattern, raw=raw_event + ) func.assert_called_once() @@ -86,37 +75,23 @@ def init(): hello = "world" attributes = { - "specversion": - "1.0", + "specversion": "1.0", # pylint: disable=protected-access - "type": - firestore_fn._event_type_created, - "source": - "https://example.com/testevent", - "time": - "2023-03-11T13:25:37.403Z", - "subject": - "test_subject", - "datacontenttype": - "application/json", - "location": - "projects/project-id/databases/(default)/documents/foo/{bar}", - "project": - "project-id", - "namespace": - "(default)", - "document": - "foo/{bar}", - "database": - "projects/project-id/databases/(default)", - "authtype": - "unauthenticated", - "authid": - "foo" + "type": firestore_fn._event_type_created, + "source": "https://example.com/testevent", + "time": "2023-03-11T13:25:37.403Z", + "subject": "test_subject", + "datacontenttype": "application/json", + "location": "projects/project-id/databases/(default)/documents/foo/{bar}", + "project": "project-id", + "namespace": "(default)", + "document": "foo/{bar}", + "database": "projects/project-id/databases/(default)", + "authtype": "unauthenticated", + "authid": "foo", } raw_event = CloudEvent(attributes=attributes, data=json.dumps({})) - decorated_func = firestore_fn.on_document_created( - document="/foo/{bar}")(func) + decorated_func = firestore_fn.on_document_created(document="/foo/{bar}")(func) decorated_func(raw_event) diff --git a/tests/test_https_fn.py b/tests/test_https_fn.py index e128b39..8406c3f 100644 --- a/tests/test_https_fn.py +++ b/tests/test_https_fn.py @@ -31,9 +31,7 @@ def init(): environ = EnvironBuilder( method="POST", json={ - "data": { - "test": "value" - }, + "data": {"test": "value"}, }, ).get_environ() request = Request(environ) @@ -59,9 +57,7 @@ def init(): environ = EnvironBuilder( method="POST", json={ - "data": { - "test": "value" - }, + "data": {"test": "value"}, }, ).get_environ() request = Request(environ) diff --git a/tests/test_identity_fn.py b/tests/test_identity_fn.py index b71414b..4c4dc0a 100644 --- a/tests/test_identity_fn.py +++ b/tests/test_identity_fn.py @@ -12,18 +12,13 @@ token_verifier_mock = MagicMock() token_verifier_mock.verify_auth_blocking_token = Mock( return_value={ - "user_record": { - "uid": "uid", - "metadata": { - "creation_time": 0 - }, - "provider_data": [] - }, + "user_record": {"uid": "uid", "metadata": {"creation_time": 0}, "provider_data": []}, "event_id": "event_id", "ip_address": "ip_address", "user_agent": "user_agent", - "iat": 0 - }) + "iat": 0, + } +) mocked_modules = { "firebase_functions.private.token_verifier": token_verifier_mock, } @@ -45,16 +40,13 @@ def init(): with patch.dict("sys.modules", mocked_modules): app = Flask(__name__) - func = Mock(__name__="example_func", - return_value=identity_fn.BeforeSignInResponse()) + func = Mock(__name__="example_func", return_value=identity_fn.BeforeSignInResponse()) with app.test_request_context("/"): environ = EnvironBuilder( method="POST", json={ - "data": { - "jwt": "jwt" - }, + "data": {"jwt": "jwt"}, }, ).get_environ() request = Request(environ) diff --git a/tests/test_init.py b/tests/test_init.py index 07ee924..7fcd4a9 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -12,7 +12,6 @@ class TestInit(unittest.TestCase): """ def test_init_is_initialized(self): - @core.init def fn(): pass diff --git a/tests/test_logger.py b/tests/test_logger.py index 9f99521..d580ab5 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -13,8 +13,7 @@ class TestLogger: Tests for the logger module. """ - def test_format_should_be_valid_json(self, - capsys: pytest.CaptureFixture[str]): + def test_format_should_be_valid_json(self, capsys: pytest.CaptureFixture[str]): logger.log(foo="bar") raw_log_output = capsys.readouterr().out try: @@ -34,8 +33,7 @@ def test_severity_should_be_debug(self, capsys: pytest.CaptureFixture[str]): log_output = json.loads(raw_log_output) assert log_output["severity"] == "DEBUG" - def test_severity_should_be_notice(self, - capsys: pytest.CaptureFixture[str]): + def test_severity_should_be_notice(self, capsys: pytest.CaptureFixture[str]): logger.log(foo="bar") raw_log_output = capsys.readouterr().out log_output = json.loads(raw_log_output) @@ -47,8 +45,7 @@ def test_severity_should_be_info(self, capsys: pytest.CaptureFixture[str]): log_output = json.loads(raw_log_output) assert log_output["severity"] == "INFO" - def test_severity_should_be_warning(self, - capsys: pytest.CaptureFixture[str]): + def test_severity_should_be_warning(self, capsys: pytest.CaptureFixture[str]): logger.warn(foo="bar") raw_log_output = capsys.readouterr().out log_output = json.loads(raw_log_output) @@ -66,23 +63,20 @@ def test_log_should_have_message(self, capsys: pytest.CaptureFixture[str]): log_output = json.loads(raw_log_output) assert "message" in log_output - def test_log_should_have_other_keys(self, - capsys: pytest.CaptureFixture[str]): + def test_log_should_have_other_keys(self, capsys: pytest.CaptureFixture[str]): logger.log(foo="bar") raw_log_output = capsys.readouterr().out log_output = json.loads(raw_log_output) assert "foo" in log_output - def test_message_should_be_space_separated( - self, capsys: pytest.CaptureFixture[str]): + def test_message_should_be_space_separated(self, capsys: pytest.CaptureFixture[str]): logger.log("bar", "qux") expected_message = "bar qux" raw_log_output = capsys.readouterr().out log_output = json.loads(raw_log_output) assert log_output["message"] == expected_message - def test_remove_circular_references(self, - capsys: pytest.CaptureFixture[str]): + def test_remove_circular_references(self, capsys: pytest.CaptureFixture[str]): # Create an object with a circular reference. circ = {"b": "foo"} circ["circ"] = circ @@ -99,15 +93,11 @@ def test_remove_circular_references(self, expected = { "severity": "ERROR", "message": "testing circular", - "circ": { - "b": "foo", - "circ": "[CIRCULAR]" - }, + "circ": {"b": "foo", "circ": "[CIRCULAR]"}, } assert log_output == expected - def test_remove_circular_references_in_arrays( - self, capsys: pytest.CaptureFixture[str]): + def test_remove_circular_references_in_arrays(self, capsys: pytest.CaptureFixture[str]): # Create an object with a circular reference inside an array. circ = {"b": "foo"} circ["circ"] = [circ] @@ -124,15 +114,11 @@ def test_remove_circular_references_in_arrays( expected = { "severity": "ERROR", "message": "testing circular", - "circ": { - "b": "foo", - "circ": ["[CIRCULAR]"] - }, + "circ": {"b": "foo", "circ": ["[CIRCULAR]"]}, } assert log_output == expected - def test_no_false_circular_for_duplicates( - self, capsys: pytest.CaptureFixture[str]): + def test_no_false_circular_for_duplicates(self, capsys: pytest.CaptureFixture[str]): # Ensure that duplicate objects (used in multiple keys) are not marked as circular. obj = {"a": "foo"} entry = { @@ -148,28 +134,17 @@ def test_no_false_circular_for_duplicates( expected = { "severity": "ERROR", "message": "testing circular", - "a": { - "a": "foo" - }, - "b": { - "a": "foo" - }, + "a": {"a": "foo"}, + "b": {"a": "foo"}, } assert log_output == expected - def test_no_false_circular_in_array_duplicates( - self, capsys: pytest.CaptureFixture[str]): + def test_no_false_circular_in_array_duplicates(self, capsys: pytest.CaptureFixture[str]): # Ensure that duplicate objects in arrays are not falsely detected as circular. obj = {"a": "foo"} arr = [ - { - "a": obj, - "b": obj - }, - { - "a": obj, - "b": obj - }, + {"a": obj, "b": obj}, + {"a": obj, "b": obj}, ] entry = { "severity": "ERROR", @@ -182,45 +157,15 @@ def test_no_false_circular_in_array_duplicates( log_output = json.loads(raw_log_output) expected = { - "severity": - "ERROR", - "message": - "testing circular", + "severity": "ERROR", + "message": "testing circular", "a": [ - { - "a": { - "a": "foo" - }, - "b": { - "a": "foo" - } - }, - { - "a": { - "a": "foo" - }, - "b": { - "a": "foo" - } - }, + {"a": {"a": "foo"}, "b": {"a": "foo"}}, + {"a": {"a": "foo"}, "b": {"a": "foo"}}, ], "b": [ - { - "a": { - "a": "foo" - }, - "b": { - "a": "foo" - } - }, - { - "a": { - "a": "foo" - }, - "b": { - "a": "foo" - } - }, + {"a": {"a": "foo"}, "b": {"a": "foo"}}, + {"a": {"a": "foo"}, "b": {"a": "foo"}}, ], } assert log_output == expected diff --git a/tests/test_manifest.py b/tests/test_manifest.py index f948b66..296cf8f 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -33,9 +33,7 @@ labels={ "hello": "world", }, - secretEnvironmentVariables=[{ - "key": "MY_SECRET" - }], + secretEnvironmentVariables=[{"key": "MY_SECRET"}], ) full_endpoint_dict = { @@ -55,9 +53,7 @@ "labels": { "hello": "world", }, - "secretEnvironmentVariables": [{ - "key": "MY_SECRET" - }], + "secretEnvironmentVariables": [{"key": "MY_SECRET"}], } full_stack = _manifest.ManifestStack( @@ -70,43 +66,29 @@ _params.StringParam("STRING_TEST"), _params.ListParam("LIST_TEST", default=["1", "2", "3"]), ], - requiredAPIs=[{ - "api": "test_api", - "reason": "testing" - }]) + requiredAPIs=[{"api": "test_api", "reason": "testing"}], +) full_stack_dict = { "specVersion": "v1alpha1", - "endpoints": { - "test": full_endpoint_dict - }, - "params": [{ - "name": "BOOL_TEST", - "type": "boolean", - "default": False, - }, { - "name": "INT_TEST", - "type": "int", - "description": "int_description" - }, { - "name": "FLOAT_TEST", - "type": "float", - "immutable": True, - }, { - "name": "SECRET_TEST", - "type": "secret" - }, { - "name": "STRING_TEST", - "type": "string" - }, { - "default": ["1", "2", "3"], - "name": "LIST_TEST", - "type": "list" - }], - "requiredAPIs": [{ - "api": "test_api", - "reason": "testing" - }] + "endpoints": {"test": full_endpoint_dict}, + "params": [ + { + "name": "BOOL_TEST", + "type": "boolean", + "default": False, + }, + {"name": "INT_TEST", "type": "int", "description": "int_description"}, + { + "name": "FLOAT_TEST", + "type": "float", + "immutable": True, + }, + {"name": "SECRET_TEST", "type": "secret"}, + {"name": "STRING_TEST", "type": "string"}, + {"default": ["1", "2", "3"], "name": "LIST_TEST", "type": "list"}, + ], + "requiredAPIs": [{"api": "test_api", "reason": "testing"}], } @@ -116,8 +98,9 @@ class TestManifestStack: def test_stack_to_dict(self): """Generic check that all ManifestStack values convert to dict.""" stack_dict = _manifest.manifest_to_spec_dict(full_stack) - assert (stack_dict == full_stack_dict - ), "Generated manifest spec dict does not match expected dict." + assert stack_dict == full_stack_dict, ( + "Generated manifest spec dict does not match expected dict." + ) class TestManifestEndpoint: @@ -127,38 +110,37 @@ def test_endpoint_to_dict(self): """Generic check that all ManifestEndpoint values convert to dict.""" # pylint: disable=protected-access endpoint_dict = _manifest._dataclass_to_spec(full_endpoint) - assert (endpoint_dict == full_endpoint_dict - ), "Generated endpoint spec dict does not match expected dict." + assert endpoint_dict == full_endpoint_dict, ( + "Generated endpoint spec dict does not match expected dict." + ) def test_endpoint_expressions(self): """Check Expression values convert to CEL strings.""" max_param = _params.IntParam("MAX") expressions_test = _manifest.ManifestEndpoint( - availableMemoryMb=_params.TernaryExpression( - _params.BoolParam("LARGE_BOOL"), 1024, 256), - minInstances=_params.StringParam("LARGE_STR").equals("yes").then( - 6, 1), + availableMemoryMb=_params.TernaryExpression(_params.BoolParam("LARGE_BOOL"), 1024, 256), + minInstances=_params.StringParam("LARGE_STR").equals("yes").then(6, 1), maxInstances=max_param.compare(">", 6).then(6, max_param), timeoutSeconds=_params.IntParam("WORLD"), concurrency=_params.IntParam("BAR"), - vpc={"connector": _params.SecretParam("SECRET")}) + vpc={"connector": _params.SecretParam("SECRET")}, + ) expressions_expected_dict = { "platform": "gcfv2", "region": [], "secretEnvironmentVariables": [], "availableMemoryMb": "{{ params.LARGE_BOOL ? 1024 : 256 }}", - "minInstances": "{{ params.LARGE_STR == \"yes\" ? 6 : 1 }}", + "minInstances": '{{ params.LARGE_STR == "yes" ? 6 : 1 }}', "maxInstances": "{{ params.MAX > 6 ? 6 : params.MAX }}", "timeoutSeconds": "{{ params.WORLD }}", "concurrency": "{{ params.BAR }}", - "vpc": { - "connector": "{{ params.SECRET }}" - } + "vpc": {"connector": "{{ params.SECRET }}"}, } # pylint: disable=protected-access expressions_actual_dict = _manifest._dataclass_to_spec(expressions_test) - assert (expressions_actual_dict == expressions_expected_dict - ), "Generated endpoint spec dict does not match expected dict." + assert expressions_actual_dict == expressions_expected_dict, ( + "Generated endpoint spec dict does not match expected dict." + ) def test_endpoint_nones(self): """Check all None values are removed.""" @@ -175,5 +157,6 @@ def test_endpoint_nones(self): } # pylint: disable=protected-access expressions_actual_dict = _manifest._dataclass_to_spec(expressions_test) - assert (expressions_actual_dict == expressions_expected_dict - ), "Generated endpoint spec dict does not match expected dict." + assert expressions_actual_dict == expressions_expected_dict, ( + "Generated endpoint spec dict does not match expected dict." + ) diff --git a/tests/test_options.py b/tests/test_options.py index baaf64a..20dba5f 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -14,6 +14,7 @@ """ Options unit tests. """ + from firebase_functions import options, https_fn from firebase_functions import params from firebase_functions.private.serving import functions_as_yaml, merge_required_apis @@ -47,19 +48,18 @@ def test_global_options_merged_with_provider_options(): options.set_global_options(max_instances=66) pubsub_options = options.PubSubOptions(topic="foo") # pylint: disable=unexpected-keyword-arg pubsub_options_dict = pubsub_options._asdict_with_global_options() - assert (pubsub_options_dict["topic"] == "foo" - ), "'topic' property missing from dict" + assert pubsub_options_dict["topic"] == "foo", "'topic' property missing from dict" assert "options" not in pubsub_options_dict, "'options' key should not exist in dict" - assert (pubsub_options_dict["max_instances"] == 66 - ), "provider option did not update using the global option" + assert pubsub_options_dict["max_instances"] == 66, ( + "provider option did not update using the global option" + ) def test_https_options_removes_cors(): """ Testing _HttpsOptions strips out the 'cors' property when converted to a dict. """ - https_options = options.HttpsOptions(cors=options.CorsOptions( - cors_origins="*")) + https_options = options.HttpsOptions(cors=options.CorsOptions(cors_origins="*")) assert https_options.cors.cors_origins == "*", "cors options were not set" https_options_dict = https_options._asdict_with_global_options() assert "cors" not in https_options_dict, "'cors' key should not exist in dict" @@ -71,25 +71,25 @@ def test_options_asdict_uses_cel_representation(): CEL values for manifest representation. """ int_param = params.IntParam("MIN") - https_options_dict = options.HttpsOptions( - min_instances=int_param)._asdict_with_global_options() - assert https_options_dict[ - "min_instances"] == f"{int_param}", "param was not converted to CEL string" + https_options_dict = options.HttpsOptions(min_instances=int_param)._asdict_with_global_options() + assert https_options_dict["min_instances"] == f"{int_param}", ( + "param was not converted to CEL string" + ) def test_options_preserve_external_changes(): """ Testing if setting a global option internally change the values. """ - assert (options._GLOBAL_OPTIONS.preserve_external_changes - is None), "option should not already be set" + assert options._GLOBAL_OPTIONS.preserve_external_changes is None, ( + "option should not already be set" + ) options.set_global_options( preserve_external_changes=False, min_instances=5, ) options_asdict = options._GLOBAL_OPTIONS._asdict_with_global_options() - assert (options_asdict["max_instances"] - is options.RESET_VALUE), "option should be RESET_VALUE" + assert options_asdict["max_instances"] is options.RESET_VALUE, "option should be RESET_VALUE" assert options_asdict["min_instances"] == 5, "option should be set" firebase_functions = { @@ -132,33 +132,15 @@ def test_merge_apis_no_duplicate_apis(): APIs without modification when there is no duplication. """ required_apis = [ - { - "api": "API1", - "reason": "Reason 1" - }, - { - "api": "API2", - "reason": "Reason 2" - }, - { - "api": "API3", - "reason": "Reason 3" - }, + {"api": "API1", "reason": "Reason 1"}, + {"api": "API2", "reason": "Reason 2"}, + {"api": "API3", "reason": "Reason 3"}, ] expected_output = [ - { - "api": "API1", - "reason": "Reason 1" - }, - { - "api": "API2", - "reason": "Reason 2" - }, - { - "api": "API3", - "reason": "Reason 3" - }, + {"api": "API1", "reason": "Reason 1"}, + {"api": "API2", "reason": "Reason 2"}, + {"api": "API3", "reason": "Reason 3"}, ] merged_apis = merge_required_apis(required_apis) @@ -176,48 +158,32 @@ def test_merge_apis_duplicate_apis(): APIs and combines the reasons associated with them. """ required_apis = [ - { - "api": "API1", - "reason": "Reason 1" - }, - { - "api": "API2", - "reason": "Reason 2" - }, - { - "api": "API1", - "reason": "Reason 3" - }, - { - "api": "API2", - "reason": "Reason 4" - }, + {"api": "API1", "reason": "Reason 1"}, + {"api": "API2", "reason": "Reason 2"}, + {"api": "API1", "reason": "Reason 3"}, + {"api": "API2", "reason": "Reason 4"}, ] expected_output = [ - { - "api": "API1", - "reason": "Reason 1 Reason 3" - }, - { - "api": "API2", - "reason": "Reason 2 Reason 4" - }, + {"api": "API1", "reason": "Reason 1 Reason 3"}, + {"api": "API2", "reason": "Reason 2 Reason 4"}, ] merged_apis = merge_required_apis(required_apis) - assert len(merged_apis) == len( - expected_output - ), f"Expected a list of length {len(expected_output)}, but got {len(merged_apis)}" + assert len(merged_apis) == len(expected_output), ( + f"Expected a list of length {len(expected_output)}, but got {len(merged_apis)}" + ) for expected_item in expected_output: - assert (expected_item in merged_apis - ), f"Expected item {expected_item} missing from the merged list" + assert expected_item in merged_apis, ( + f"Expected item {expected_item} missing from the merged list" + ) for actual_item in merged_apis: - assert (actual_item in expected_output - ), f"Unexpected item {actual_item} found in the merged list" + assert actual_item in expected_output, ( + f"Unexpected item {actual_item} found in the merged list" + ) def test_invoker_with_one_element_doesnt_throw(): @@ -226,8 +192,6 @@ def test_invoker_with_one_element_doesnt_throw(): def test_invoker_with_no_element_throws(): with raises( - AssertionError, - match= - "HttpsOptions: Invalid option for invoker - must be a non-empty list." + AssertionError, match="HttpsOptions: Invalid option for invoker - must be a non-empty list." ): options.HttpsOptions(invoker=[])._endpoint(func_name="test") diff --git a/tests/test_params.py b/tests/test_params.py index 92e8103..11b2552 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Param unit tests.""" + from os import environ import pytest @@ -24,32 +25,33 @@ class TestBoolParams: def test_bool_param_value_true_or_false(self): """Testing if bool params correctly returns a true or false value.""" bool_param = params.BoolParam("BOOL_VALUE_TEST1") - for value_true, value_false in zip(["true"], - ["false", "anything", "else"]): + for value_true, value_false in zip(["true"], ["false", "anything", "else"]): environ["BOOL_VALUE_TEST1"] = value_true - assert (bool_param.value is True), "Failure, params returned False" + assert bool_param.value is True, "Failure, params returned False" environ["BOOL_VALUE_TEST1"] = value_false - assert (bool_param.value is False), "Failure, params returned True" + assert bool_param.value is False, "Failure, params returned True" def test_bool_param_empty_default(self): """Testing if bool params defaults to False if no value and no default.""" - assert (params.BoolParam("BOOL_DEFAULT_TEST").value - is False), "Failure, params returned True" + assert params.BoolParam("BOOL_DEFAULT_TEST").value is False, "Failure, params returned True" def test_bool_param_default(self): """Testing if bool params defaults to provided default value.""" - assert (params.BoolParam("BOOL_DEFAULT_TEST_FALSE", default=False).value - is False), "Failure, params returned True" - assert (params.BoolParam("BOOL_DEFAULT_TEST_TRUE", default=True).value - is True), "Failure, params returned False" + assert params.BoolParam("BOOL_DEFAULT_TEST_FALSE", default=False).value is False, ( + "Failure, params returned True" + ) + assert params.BoolParam("BOOL_DEFAULT_TEST_TRUE", default=True).value is True, ( + "Failure, params returned False" + ) def test_bool_param_equality(self): """Test bool equality.""" - assert (params.BoolParam("BOOL_TEST1", - default=False).equals(False).value - is True), "Failure, equality check returned False" - assert (params.BoolParam("BOOL_TEST2", default=True).equals(False).value - is False), "Failure, equality check returned False" + assert params.BoolParam("BOOL_TEST1", default=False).equals(False).value is True, ( + "Failure, equality check returned False" + ) + assert params.BoolParam("BOOL_TEST2", default=True).equals(False).value is False, ( + "Failure, equality check returned False" + ) class TestFloatParams: @@ -58,28 +60,33 @@ class TestFloatParams: def test_float_param_value(self): """Testing if float params correctly returns a value.""" environ["FLOAT_VALUE_TEST"] = "123.456" - assert params._FloatParam("FLOAT_VALUE_TEST",).value == 123.456, \ - "Failure, params value != 123.456" + assert ( + params._FloatParam( + "FLOAT_VALUE_TEST", + ).value + == 123.456 + ), "Failure, params value != 123.456" def test_float_param_empty_default(self): """Testing if float params defaults to empty float if no value and no default.""" - assert params._FloatParam("FLOAT_DEFAULT_TEST1").value == float(), \ + assert params._FloatParam("FLOAT_DEFAULT_TEST1").value == float(), ( "Failure, params value is not float" + ) def test_float_param_default(self): """Testing if float param defaults to provided default value.""" - assert params._FloatParam("FLOAT_DEFAULT_TEST2", \ - default=float(456.789)).value == 456.789, \ + assert params._FloatParam("FLOAT_DEFAULT_TEST2", default=float(456.789)).value == 456.789, ( "Failure, params default value != 456.789" + ) def test_float_param_equality(self): """Test float equality.""" - assert (params._FloatParam("FLOAT_TEST1", \ - default=123.456).equals(123.456).value \ - is True), "Failure, equality check returned False" - assert (params._FloatParam("FLOAT_TEST2", \ - default=456.789).equals(123.456).value \ - is False), "Failure, equality check returned False" + assert params._FloatParam("FLOAT_TEST1", default=123.456).equals(123.456).value is True, ( + "Failure, equality check returned False" + ) + assert params._FloatParam("FLOAT_TEST2", default=456.789).equals(123.456).value is False, ( + "Failure, equality check returned False" + ) class TestIntParams: @@ -88,25 +95,28 @@ class TestIntParams: def test_int_param_value(self): """Testing if int param correctly returns a value.""" environ["INT_VALUE_TEST"] = "123" - assert params.IntParam( - "INT_VALUE_TEST").value == 123, "Failure, params value != 123" + assert params.IntParam("INT_VALUE_TEST").value == 123, "Failure, params value != 123" def test_int_param_empty_default(self): """Testing if int param defaults to empty int if no value and no default.""" - assert params.IntParam("INT_DEFAULT_TEST1").value == int( - ), "Failure, params value is not int" + assert params.IntParam("INT_DEFAULT_TEST1").value == int(), ( + "Failure, params value is not int" + ) def test_int_param_default(self): """Testing if int param defaults to provided default value.""" - assert params.IntParam("INT_DEFAULT_TEST2", default=456).value == 456, \ + assert params.IntParam("INT_DEFAULT_TEST2", default=456).value == 456, ( "Failure, params default value != 456" + ) def test_int_param_equality(self): """Test int equality.""" - assert (params.IntParam("INT_TEST1", default=123).equals(123).value - is True), "Failure, equality check returned False" - assert (params.IntParam("INT_TEST2", default=456).equals(123).value - is False), "Failure, equality check returned False" + assert params.IntParam("INT_TEST1", default=123).equals(123).value is True, ( + "Failure, equality check returned False" + ) + assert params.IntParam("INT_TEST2", default=456).equals(123).value is False, ( + "Failure, equality check returned False" + ) class TestStringParams: @@ -115,8 +125,9 @@ class TestStringParams: def test_string_param_value(self): """Testing if string param correctly returns a value.""" environ["STRING_VALUE_TEST"] = "STRING_TEST" - assert params.StringParam("STRING_VALUE_TEST").value == "STRING_TEST", \ + assert params.StringParam("STRING_VALUE_TEST").value == "STRING_TEST", ( 'Failure, params value != "STRING_TEST"' + ) def test_param_name_upper_snake_case(self): """Testing if param names are validated to be upper snake case.""" @@ -126,24 +137,25 @@ def test_param_name_upper_snake_case(self): def test_string_param_empty_default(self): """Testing if string param defaults to empty string if no value and no default.""" - assert params.StringParam("STRING_DEFAULT_TEST1").value == str(), \ + assert params.StringParam("STRING_DEFAULT_TEST1").value == str(), ( "Failure, params value is not a string" + ) def test_string_param_default(self): """Testing if string param defaults to provided default value.""" - assert (params.StringParam("STRING_DEFAULT_TEST2", - default="string_override_default").value - == "string_override_default"), \ - 'Failure, params default value != "string_override_default"' + assert ( + params.StringParam("STRING_DEFAULT_TEST2", default="string_override_default").value + == "string_override_default" + ), 'Failure, params default value != "string_override_default"' def test_string_param_equality(self): """Test string equality.""" - assert (params.StringParam("STRING_TEST1", - default="123").equals("123").value - is True), "Failure, equality check returned False" - assert (params.StringParam("STRING_TEST2", - default="456").equals("123").value - is False), "Failure, equality check returned False" + assert params.StringParam("STRING_TEST1", default="123").equals("123").value is True, ( + "Failure, equality check returned False" + ) + assert params.StringParam("STRING_TEST2", default="456").equals("123").value is False, ( + "Failure, equality check returned False" + ) class TestListParams: @@ -152,34 +164,37 @@ class TestListParams: def test_list_param_value(self): """Testing if list param correctly returns list values.""" environ["LIST_VALUE_TEST1"] = "item1,item2" - assert params.ListParam("LIST_VALUE_TEST1").value == ["item1","item2"], \ + assert params.ListParam("LIST_VALUE_TEST1").value == ["item1", "item2"], ( 'Failure, params value != ["item1","item2"]' + ) def test_list_param_filter_empty_strings(self): """Testing if list param correctly returns list values wth empty strings excluded.""" environ["LIST_VALUE_TEST2"] = ",,item1,item2,,,item3," - assert params.ListParam("LIST_VALUE_TEST2").value == ["item1","item2", "item3"], \ + assert params.ListParam("LIST_VALUE_TEST2").value == ["item1", "item2", "item3"], ( 'Failure, params value != ["item1","item2", "item3"]' + ) def test_list_param_empty_default(self): """Testing if list param defaults to an empty list if no value and no default.""" - assert params.ListParam("LIST_DEFAULT_TEST1").value == [], \ + assert params.ListParam("LIST_DEFAULT_TEST1").value == [], ( "Failure, params value is not an empty list" + ) def test_list_param_default(self): """Testing if list param defaults to the provided default value.""" - assert (params.ListParam("LIST_DEFAULT_TEST2", default=["1", "2"]).value - == ["1", "2"]), \ + assert params.ListParam("LIST_DEFAULT_TEST2", default=["1", "2"]).value == ["1", "2"], ( 'Failure, params default value != ["1", "2"]' + ) def test_list_param_equality(self): """Test list equality.""" - assert (params.ListParam("LIST_TEST1", - default=["123"]).equals(["123"]).value - is True), "Failure, equality check returned False" - assert (params.ListParam("LIST_TEST2", - default=["456"]).equals(["123"]).value - is False), "Failure, equality check returned False" + assert params.ListParam("LIST_TEST1", default=["123"]).equals(["123"]).value is True, ( + "Failure, equality check returned False" + ) + assert params.ListParam("LIST_TEST2", default=["456"]).equals(["123"]).value is False, ( + "Failure, equality check returned False" + ) class TestParamsManifest: @@ -192,18 +207,18 @@ def test_params_stored(self): """Testing if params are internally stored.""" environ["TEST_STORING"] = "TEST_STORING_VALUE" param = params.StringParam("TEST_STORING") - assert param.value == "TEST_STORING_VALUE", \ - 'Failure, params value != "TEST_STORING_VALUE"' + assert param.value == "TEST_STORING_VALUE", 'Failure, params value != "TEST_STORING_VALUE"' # pylint: disable=protected-access - assert params._params["TEST_STORING"] == param, \ - "Failure, param was not stored" + assert params._params["TEST_STORING"] == param, "Failure, param was not stored" def test_default_params_not_stored(self): """Testing if default params are skipped from being stored.""" environ["GCLOUD_PROJECT"] = "python-testing-project" - assert params.PROJECT_ID.value == "python-testing-project", \ + assert params.PROJECT_ID.value == "python-testing-project", ( 'Failure, params value != "python-testing-project"' + ) # pylint: disable=protected-access - assert params._params.get("GCLOUD_PROJECT") is None, \ + assert params._params.get("GCLOUD_PROJECT") is None, ( "Failure, default param was stored when it should not have been" + ) diff --git a/tests/test_path_pattern.py b/tests/test_path_pattern.py index 3513e34..20c88e9 100644 --- a/tests/test_path_pattern.py +++ b/tests/test_path_pattern.py @@ -77,8 +77,7 @@ def test_extract_matches(self): # parse multi segment with params after pp = PathPattern("something/**/else/{a}/hello/{b}/world") self.assertEqual( - pp.extract_matches( - "something/is/a/thing/else/nothing/hello/user/world"), + pp.extract_matches("something/is/a/thing/else/nothing/hello/user/world"), { "a": "nothing", "b": "user", @@ -88,8 +87,7 @@ def test_extract_matches(self): # parse multi-capture segment with params after pp = PathPattern("something/{path=**}/else/{a}/hello/{b}/world") self.assertEqual( - pp.extract_matches( - "something/is/a/thing/else/nothing/hello/user/world"), + pp.extract_matches("something/is/a/thing/else/nothing/hello/user/world"), { "path": "is/a/thing", "a": "nothing", @@ -100,8 +98,7 @@ def test_extract_matches(self): # parse multi segment with params before pp = PathPattern("{a}/something/{b}/**/end") self.assertEqual( - pp.extract_matches( - "match_a/something/match_b/thing/else/nothing/hello/user/end"), + pp.extract_matches("match_a/something/match_b/thing/else/nothing/hello/user/end"), { "a": "match_a", "b": "match_b", @@ -111,8 +108,7 @@ def test_extract_matches(self): # parse multi-capture segment with params before pp = PathPattern("{a}/something/{b}/{path=**}/end") self.assertEqual( - pp.extract_matches( - "match_a/something/match_b/thing/else/nothing/hello/user/end"), + pp.extract_matches("match_a/something/match_b/thing/else/nothing/hello/user/end"), { "a": "match_a", "b": "match_b", @@ -123,8 +119,7 @@ def test_extract_matches(self): # parse multi segment with params before and after pp = PathPattern("{a}/something/**/{b}/end") self.assertEqual( - pp.extract_matches( - "match_a/something/thing/else/nothing/hello/user/match_b/end"), + pp.extract_matches("match_a/something/thing/else/nothing/hello/user/match_b/end"), { "a": "match_a", "b": "match_b", @@ -134,8 +129,7 @@ def test_extract_matches(self): # parse multi-capture segment with params before and after pp = PathPattern("{a}/something/{path=**}/{b}/end") self.assertEqual( - pp.extract_matches( - "match_a/something/thing/else/nothing/hello/user/match_b/end"), + pp.extract_matches("match_a/something/thing/else/nothing/hello/user/match_b/end"), { "a": "match_a", "b": "match_b", diff --git a/tests/test_pubsub_fn.py b/tests/test_pubsub_fn.py index 74ae72e..85b7553 100644 --- a/tests/test_pubsub_fn.py +++ b/tests/test_pubsub_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """PubSub function tests.""" + import unittest import datetime as _dt from unittest.mock import MagicMock @@ -44,8 +45,7 @@ def test_on_message_published_decorator(self): self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.eventTrigger) self.assertIsNotNone(endpoint.eventTrigger["eventType"]) - self.assertEqual("hello-world", - endpoint.eventTrigger["eventFilters"]["topic"]) + self.assertEqual("hello-world", endpoint.eventTrigger["eventFilters"]["topic"]) def test_message_handler(self): """ @@ -64,9 +64,7 @@ def test_message_handler(self): }, data={ "message": { - "attributes": { - "key": "value" - }, + "attributes": {"key": "value"}, # {"test": "value"} "data": "eyJ0ZXN0IjogInZhbHVlIn0=", "message_id": "message-id-123", @@ -88,11 +86,10 @@ def test_message_handler(self): _dt.datetime.strptime( "2023-03-11T13:25:37.403Z", "%Y-%m-%dT%H:%M:%S.%f%z", - )) - self.assertDictEqual(event_arg.data.message.attributes, - {"key": "value"}) - self.assertEqual(event_arg.data.message.data, - "eyJ0ZXN0IjogInZhbHVlIn0=") + ), + ) + self.assertDictEqual(event_arg.data.message.attributes, {"key": "value"}) + self.assertEqual(event_arg.data.message.data, "eyJ0ZXN0IjogInZhbHVlIn0=") self.assertIsNone(event_arg.data.message.ordering_key) self.assertEqual(event_arg.data.subscription, "my-subscription") @@ -115,9 +112,7 @@ def init(): }, data={ "message": { - "attributes": { - "key": "value" - }, + "attributes": {"key": "value"}, "data": "eyJ0ZXN0IjogInZhbHVlIn0=", "message_id": "message-id-123", "publish_time": "2023-03-11T13:25:37.403Z", @@ -142,9 +137,7 @@ def test_datetime_without_mircroseconds_doesnt_throw(self): }, data={ "message": { - "attributes": { - "key": "value" - }, + "attributes": {"key": "value"}, "data": "eyJ0ZXN0IjogInZhbHVlIn0=", "message_id": "message-id-123", "publish_time": time, @@ -156,5 +149,4 @@ def test_datetime_without_mircroseconds_doesnt_throw(self): _message_handler(lambda _: None, raw_event) # pylint: disable=broad-except except Exception: - self.fail( - "Datetime without microseconds should not throw an exception") + self.fail("Datetime without microseconds should not throw an exception") diff --git a/tests/test_remote_config_fn.py b/tests/test_remote_config_fn.py index 2854a0a..be0341a 100644 --- a/tests/test_remote_config_fn.py +++ b/tests/test_remote_config_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Remote Config function tests.""" + import unittest from unittest.mock import MagicMock from cloudevents.http import CloudEvent as _CloudEvent @@ -66,13 +67,14 @@ def test_config_handler(self): "updateUser": { "name": "John Doe", "email": "johndoe@example.com", - "imageUrl": "https://example.com/image.jpg" + "imageUrl": "https://example.com/image.jpg", }, "description": "Test update", "updateOrigin": "CONSOLE", "updateType": "INCREMENTAL_UPDATE", - "rollbackSource": 41 - }) + "rollbackSource": 41, + }, + ) _config_handler(func, raw_event) @@ -83,8 +85,6 @@ def test_config_handler(self): self.assertIsInstance(event_arg.data, ConfigUpdateData) self.assertIsInstance(event_arg.data.update_user, ConfigUser) self.assertEqual(event_arg.data.version_number, 42) - self.assertEqual(event_arg.data.update_origin, - ConfigUpdateOrigin.CONSOLE) - self.assertEqual(event_arg.data.update_type, - ConfigUpdateType.INCREMENTAL_UPDATE) + self.assertEqual(event_arg.data.update_origin, ConfigUpdateOrigin.CONSOLE) + self.assertEqual(event_arg.data.update_type, ConfigUpdateType.INCREMENTAL_UPDATE) self.assertEqual(event_arg.data.rollback_source, 41) diff --git a/tests/test_scheduler_fn.py b/tests/test_scheduler_fn.py index 56853c6..8f52fc7 100644 --- a/tests/test_scheduler_fn.py +++ b/tests/test_scheduler_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Scheduler function tests.""" + import unittest from unittest.mock import Mock from datetime import datetime @@ -35,8 +36,8 @@ def test_on_schedule_decorator(self): tz = "America/Los_Angeles" example_func = Mock(__name__="example_func") decorated_func = scheduler_fn.on_schedule( - schedule="* * * * *", - timezone=scheduler_fn.Timezone(tz))(example_func) + schedule="* * * * *", timezone=scheduler_fn.Timezone(tz) + )(example_func) endpoint = getattr(decorated_func, "__firebase_endpoint__") self.assertIsNotNone(endpoint) @@ -55,12 +56,12 @@ def test_on_schedule_call(self): environ = EnvironBuilder( headers={ "X-CloudScheduler-JobName": "example-job", - "X-CloudScheduler-ScheduleTime": "2023-04-13T12:00:00-07:00" - }).get_environ() + "X-CloudScheduler-ScheduleTime": "2023-04-13T12:00:00-07:00", + } + ).get_environ() mock_request = Request(environ) example_func = Mock(__name__="example_func") - decorated_func = scheduler_fn.on_schedule( - schedule="* * * * *")(example_func) + decorated_func = scheduler_fn.on_schedule(schedule="* * * * *")(example_func) response = decorated_func(mock_request) self.assertEqual(response.status_code, 200) @@ -75,7 +76,8 @@ def test_on_schedule_call(self): 0, tzinfo=scheduler_fn.Timezone("America/Los_Angeles"), ), - )) + ) + ) def test_on_schedule_call_with_no_headers(self): """ @@ -88,8 +90,7 @@ def test_on_schedule_call_with_no_headers(self): environ = EnvironBuilder().get_environ() mock_request = Request(environ) example_func = Mock(__name__="example_func") - decorated_func = scheduler_fn.on_schedule( - schedule="* * * * *")(example_func) + decorated_func = scheduler_fn.on_schedule(schedule="* * * * *")(example_func) response = decorated_func(mock_request) self.assertEqual(response.status_code, 200) @@ -107,13 +108,12 @@ def test_on_schedule_call_with_exception(self): environ = EnvironBuilder( headers={ "X-CloudScheduler-JobName": "example-job", - "X-CloudScheduler-ScheduleTime": "2023-04-13T12:00:00-07:00" - }).get_environ() + "X-CloudScheduler-ScheduleTime": "2023-04-13T12:00:00-07:00", + } + ).get_environ() mock_request = Request(environ) - example_func = Mock(__name__="example_func", - side_effect=Exception("Test exception")) - decorated_func = scheduler_fn.on_schedule( - schedule="* * * * *")(example_func) + example_func = Mock(__name__="example_func", side_effect=Exception("Test exception")) + decorated_func = scheduler_fn.on_schedule(schedule="* * * * *")(example_func) response = decorated_func(mock_request) self.assertEqual(response.status_code, 500) @@ -131,8 +131,7 @@ def init(): environ = EnvironBuilder().get_environ() mock_request = Request(environ) example_func = Mock(__name__="example_func") - decorated_func = scheduler_fn.on_schedule( - schedule="* * * * *")(example_func) + decorated_func = scheduler_fn.on_schedule(schedule="* * * * *")(example_func) decorated_func(mock_request) self.assertEqual("world", hello) diff --git a/tests/test_storage_fn.py b/tests/test_storage_fn.py index ec55ca8..c42498e 100644 --- a/tests/test_storage_fn.py +++ b/tests/test_storage_fn.py @@ -23,19 +23,18 @@ def init(): hello = "world" func = Mock(__name__="example_func") - event = CloudEvent(attributes={ - "source": "source", - "type": "type" - }, - data={ - "bucket": "bucket", - "generation": "generation", - "id": "id", - "metageneration": "metageneration", - "name": "name", - "size": "size", - "storageClass": "storageClass", - }) + event = CloudEvent( + attributes={"source": "source", "type": "type"}, + data={ + "bucket": "bucket", + "generation": "generation", + "id": "id", + "metageneration": "metageneration", + "name": "name", + "size": "size", + "storageClass": "storageClass", + }, + ) decorated_func = storage_fn.on_object_archived(bucket="bucket")(func) decorated_func(event) diff --git a/tests/test_tasks_fn.py b/tests/test_tasks_fn.py index b16ede3..02ad385 100644 --- a/tests/test_tasks_fn.py +++ b/tests/test_tasks_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Task Queue function tests.""" + import unittest from unittest.mock import MagicMock, Mock @@ -58,9 +59,7 @@ def example(request: CallableRequest[object]) -> str: environ = EnvironBuilder( method="POST", json={ - "data": { - "test": "value" - }, + "data": {"test": "value"}, }, ).get_environ() request = Request(environ) @@ -87,9 +86,7 @@ def init(): environ = EnvironBuilder( method="POST", json={ - "data": { - "test": "value" - }, + "data": {"test": "value"}, }, ).get_environ() request = Request(environ) diff --git a/tests/test_test_lab_fn.py b/tests/test_test_lab_fn.py index afa9836..04b64f9 100644 --- a/tests/test_test_lab_fn.py +++ b/tests/test_test_lab_fn.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Test Lab function tests.""" + import unittest from unittest.mock import MagicMock, Mock from cloudevents.http import CloudEvent as _CloudEvent @@ -68,20 +69,17 @@ def test_event_handler(self): "invalidMatrixDetails": "Some details", "outcomeSummary": "SUCCESS", "resultStorage": { - "toolResultsHistory": - "projects/123/histories/456", - "resultsUri": - "https://example.com/results", - "gcsPath": - "gs://bucket/path/to/somewhere", - "toolResultsExecution": - "projects/123/histories/456/executions/789", + "toolResultsHistory": "projects/123/histories/456", + "resultsUri": "https://example.com/results", + "gcsPath": "gs://bucket/path/to/somewhere", + "toolResultsExecution": "projects/123/histories/456/executions/789", }, "clientInfo": { "client": "gcloud", }, "testMatrixId": "testmatrix-123", - }) + }, + ) _event_handler(func, raw_event) @@ -119,20 +117,17 @@ def init(): "invalidMatrixDetails": "Some details", "outcomeSummary": "SUCCESS", "resultStorage": { - "toolResultsHistory": - "projects/123/histories/456", - "resultsUri": - "https://example.com/results", - "gcsPath": - "gs://bucket/path/to/somewhere", - "toolResultsExecution": - "projects/123/histories/456/executions/789", + "toolResultsHistory": "projects/123/histories/456", + "resultsUri": "https://example.com/results", + "gcsPath": "gs://bucket/path/to/somewhere", + "toolResultsExecution": "projects/123/histories/456/executions/789", }, "clientInfo": { "client": "gcloud", }, "testMatrixId": "testmatrix-123", - }) + }, + ) decorated_func = on_test_matrix_completed()(func) decorated_func(raw_event) diff --git a/tests/test_util.py b/tests/test_util.py index cb13d30..ec7ae1e 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -14,13 +14,23 @@ """ Internal utils tests. """ + from os import environ, path -from firebase_functions.private.util import firebase_config, microsecond_timestamp_conversion, nanoseconds_timestamp_conversion, get_precision_timestamp, normalize_path, deep_merge, PrecisionTimestamp, second_timestamp_conversion, _unsafe_decode_id_token +from firebase_functions.private.util import ( + firebase_config, + microsecond_timestamp_conversion, + nanoseconds_timestamp_conversion, + get_precision_timestamp, + normalize_path, + deep_merge, + PrecisionTimestamp, + second_timestamp_conversion, + _unsafe_decode_id_token, +) import datetime as _dt test_bucket = "python-functions-testing.appspot.com" -test_config_file = path.join(path.dirname(path.realpath(__file__)), - "firebase_config_test.json") +test_config_file = path.join(path.dirname(path.realpath(__file__)), "firebase_config_test.json") def test_firebase_config_loads_from_env_json(): @@ -30,7 +40,8 @@ def test_firebase_config_loads_from_env_json(): """ environ["FIREBASE_CONFIG"] = f'{{"storageBucket": "{test_bucket}"}}' assert firebase_config().storage_bucket == test_bucket, ( - "Failure, firebase_config did not load from env variable.") + "Failure, firebase_config did not load from env variable." + ) def test_firebase_config_loads_from_env_file(): @@ -40,7 +51,8 @@ def test_firebase_config_loads_from_env_file(): """ environ["FIREBASE_CONFIG"] = test_config_file assert firebase_config().storage_bucket == test_bucket, ( - "Failure, firebase_config did not load from env variable.") + "Failure, firebase_config did not load from env variable." + ) def test_microsecond_conversion(): @@ -55,11 +67,9 @@ def test_microsecond_conversion(): ] for input_timestamp, expected_output in timestamps: - expected_datetime = _dt.datetime.strptime(expected_output, - "%Y-%m-%dT%H:%M:%S.%fZ") + expected_datetime = _dt.datetime.strptime(expected_output, "%Y-%m-%dT%H:%M:%S.%fZ") expected_datetime = expected_datetime.replace(tzinfo=_dt.timezone.utc) - assert microsecond_timestamp_conversion( - input_timestamp) == expected_datetime + assert microsecond_timestamp_conversion(input_timestamp) == expected_datetime def test_nanosecond_conversion(): @@ -74,11 +84,9 @@ def test_nanosecond_conversion(): ] for input_timestamp, expected_output in timestamps: - expected_datetime = _dt.datetime.strptime(expected_output, - "%Y-%m-%dT%H:%M:%S.%fZ") + expected_datetime = _dt.datetime.strptime(expected_output, "%Y-%m-%dT%H:%M:%S.%fZ") expected_datetime = expected_datetime.replace(tzinfo=_dt.timezone.utc) - assert nanoseconds_timestamp_conversion( - input_timestamp) == expected_datetime + assert nanoseconds_timestamp_conversion(input_timestamp) == expected_datetime def test_second_conversion(): @@ -93,8 +101,7 @@ def test_second_conversion(): ] for input_timestamp, expected_output in timestamps: - expected_datetime = _dt.datetime.strptime(expected_output, - "%Y-%m-%dT%H:%M:%SZ") + expected_datetime = _dt.datetime.strptime(expected_output, "%Y-%m-%dT%H:%M:%SZ") expected_datetime = expected_datetime.replace(tzinfo=_dt.timezone.utc) assert second_timestamp_conversion(input_timestamp) == expected_datetime @@ -118,30 +125,18 @@ def test_is_nanoseconds_timestamp(): second_timestamp3 = "2023-03-21T06:43:58Z" second_timestamp4 = "2023-08-15T22:22:22Z" - assert get_precision_timestamp( - microsecond_timestamp1) is PrecisionTimestamp.MICROSECONDS - assert get_precision_timestamp( - microsecond_timestamp2) is PrecisionTimestamp.MICROSECONDS - assert get_precision_timestamp( - microsecond_timestamp3) is PrecisionTimestamp.MICROSECONDS - assert get_precision_timestamp( - microsecond_timestamp4) is PrecisionTimestamp.MICROSECONDS - assert get_precision_timestamp( - nanosecond_timestamp1) is PrecisionTimestamp.NANOSECONDS - assert get_precision_timestamp( - nanosecond_timestamp2) is PrecisionTimestamp.NANOSECONDS - assert get_precision_timestamp( - nanosecond_timestamp3) is PrecisionTimestamp.NANOSECONDS - assert get_precision_timestamp( - nanosecond_timestamp4) is PrecisionTimestamp.NANOSECONDS - assert get_precision_timestamp( - second_timestamp1) is PrecisionTimestamp.SECONDS - assert get_precision_timestamp( - second_timestamp2) is PrecisionTimestamp.SECONDS - assert get_precision_timestamp( - second_timestamp3) is PrecisionTimestamp.SECONDS - assert get_precision_timestamp( - second_timestamp4) is PrecisionTimestamp.SECONDS + assert get_precision_timestamp(microsecond_timestamp1) is PrecisionTimestamp.MICROSECONDS + assert get_precision_timestamp(microsecond_timestamp2) is PrecisionTimestamp.MICROSECONDS + assert get_precision_timestamp(microsecond_timestamp3) is PrecisionTimestamp.MICROSECONDS + assert get_precision_timestamp(microsecond_timestamp4) is PrecisionTimestamp.MICROSECONDS + assert get_precision_timestamp(nanosecond_timestamp1) is PrecisionTimestamp.NANOSECONDS + assert get_precision_timestamp(nanosecond_timestamp2) is PrecisionTimestamp.NANOSECONDS + assert get_precision_timestamp(nanosecond_timestamp3) is PrecisionTimestamp.NANOSECONDS + assert get_precision_timestamp(nanosecond_timestamp4) is PrecisionTimestamp.NANOSECONDS + assert get_precision_timestamp(second_timestamp1) is PrecisionTimestamp.SECONDS + assert get_precision_timestamp(second_timestamp2) is PrecisionTimestamp.SECONDS + assert get_precision_timestamp(second_timestamp3) is PrecisionTimestamp.SECONDS + assert get_precision_timestamp(second_timestamp4) is PrecisionTimestamp.SECONDS def test_normalize_document_path(): @@ -150,16 +145,15 @@ def test_normalize_document_path(): is normalized. """ test_path = "/test/document/" - assert normalize_path(test_path) == "test/document", ( - "Failure, path was not normalized.") + assert normalize_path(test_path) == "test/document", "Failure, path was not normalized." test_path1 = "//////test/document//////////" - assert normalize_path(test_path1) == "test/document", ( - "Failure, path was not normalized.") + assert normalize_path(test_path1) == "test/document", "Failure, path was not normalized." test_path2 = "test/document" assert normalize_path(test_path2) == "test/document", ( - "Failure, path should not be changed if it is already normalized.") + "Failure, path should not be changed if it is already normalized." + ) def test_toplevel_keys(): diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..cf12447 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1812 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version < '3.11'", +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, +] + +[[package]] +name = "build" +version = "1.2.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950 }, +] + +[[package]] +name = "cachecontrol" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/3a/0cbeb04ea57d2493f3ec5a069a117ab467f85e4a10017c6d854ddcbff104/cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11", size = 28985 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/4c/800b0607b00b3fd20f1087f80ab53d6b4d005515b0f773e4831e37cfa83f/cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae", size = 21802 }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818 }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649 }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045 }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471 }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317 }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368 }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491 }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695 }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849 }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091 }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445 }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782 }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, +] + +[[package]] +name = "cloudevents" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/aa/804bdb5f2f021fcc887eeabfa24bad0ffd4b150f60850ae88faa51d393a5/cloudevents-1.12.0.tar.gz", hash = "sha256:ebd5544ceb58c8378a0787b657a2ae895e929b80a82d6675cba63f0e8c5539e0", size = 34494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/b6/4e29b74bb40daa7580310a5ff0df5f121a08ce98340e01a960b668468aab/cloudevents-1.12.0-py3-none-any.whl", hash = "sha256:49196267f5f963d87ae156f93fc0fa32f4af69485f2c8e62e0db8b0b4b8b8921", size = 55762 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/78/1c1c5ec58f16817c09cbacb39783c3655d54a221b6552f47ff5ac9297603/coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca", size = 212028 }, + { url = "https://files.pythonhosted.org/packages/98/db/e91b9076f3a888e3b4ad7972ea3842297a52cc52e73fd1e529856e473510/coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509", size = 212420 }, + { url = "https://files.pythonhosted.org/packages/0e/d0/2b3733412954576b0aea0a16c3b6b8fbe95eb975d8bfa10b07359ead4252/coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b", size = 241529 }, + { url = "https://files.pythonhosted.org/packages/b3/00/5e2e5ae2e750a872226a68e984d4d3f3563cb01d1afb449a17aa819bc2c4/coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3", size = 239403 }, + { url = "https://files.pythonhosted.org/packages/37/3b/a2c27736035156b0a7c20683afe7df498480c0dfdf503b8c878a21b6d7fb/coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3", size = 240548 }, + { url = "https://files.pythonhosted.org/packages/98/f5/13d5fc074c3c0e0dc80422d9535814abf190f1254d7c3451590dc4f8b18c/coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5", size = 240459 }, + { url = "https://files.pythonhosted.org/packages/36/24/24b9676ea06102df824c4a56ffd13dc9da7904478db519efa877d16527d5/coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187", size = 239128 }, + { url = "https://files.pythonhosted.org/packages/be/05/242b7a7d491b369ac5fee7908a6e5ba42b3030450f3ad62c645b40c23e0e/coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce", size = 239402 }, + { url = "https://files.pythonhosted.org/packages/73/e0/4de7f87192fa65c9c8fbaeb75507e124f82396b71de1797da5602898be32/coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70", size = 214518 }, + { url = "https://files.pythonhosted.org/packages/d5/ab/5e4e2fe458907d2a65fab62c773671cfc5ac704f1e7a9ddd91996f66e3c2/coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe", size = 215436 }, + { url = "https://files.pythonhosted.org/packages/60/34/fa69372a07d0903a78ac103422ad34db72281c9fc625eba94ac1185da66f/coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582", size = 212146 }, + { url = "https://files.pythonhosted.org/packages/27/f0/da1894915d2767f093f081c42afeba18e760f12fdd7a2f4acbe00564d767/coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86", size = 212536 }, + { url = "https://files.pythonhosted.org/packages/10/d5/3fc33b06e41e390f88eef111226a24e4504d216ab8e5d1a7089aa5a3c87a/coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed", size = 245092 }, + { url = "https://files.pythonhosted.org/packages/0a/39/7aa901c14977aba637b78e95800edf77f29f5a380d29768c5b66f258305b/coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d", size = 242806 }, + { url = "https://files.pythonhosted.org/packages/43/fc/30e5cfeaf560b1fc1989227adedc11019ce4bb7cce59d65db34fe0c2d963/coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338", size = 244610 }, + { url = "https://files.pythonhosted.org/packages/bf/15/cca62b13f39650bc87b2b92bb03bce7f0e79dd0bf2c7529e9fc7393e4d60/coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875", size = 244257 }, + { url = "https://files.pythonhosted.org/packages/cd/1a/c0f2abe92c29e1464dbd0ff9d56cb6c88ae2b9e21becdb38bea31fcb2f6c/coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250", size = 242309 }, + { url = "https://files.pythonhosted.org/packages/57/8d/c6fd70848bd9bf88fa90df2af5636589a8126d2170f3aade21ed53f2b67a/coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c", size = 242898 }, + { url = "https://files.pythonhosted.org/packages/c2/9e/6ca46c7bff4675f09a66fe2797cd1ad6a24f14c9c7c3b3ebe0470a6e30b8/coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32", size = 214561 }, + { url = "https://files.pythonhosted.org/packages/a1/30/166978c6302010742dabcdc425fa0f938fa5a800908e39aff37a7a876a13/coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125", size = 215493 }, + { url = "https://files.pythonhosted.org/packages/60/07/a6d2342cd80a5be9f0eeab115bc5ebb3917b4a64c2953534273cf9bc7ae6/coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e", size = 213869 }, + { url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336 }, + { url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571 }, + { url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377 }, + { url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394 }, + { url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586 }, + { url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396 }, + { url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577 }, + { url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809 }, + { url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724 }, + { url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535 }, + { url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904 }, + { url = "https://files.pythonhosted.org/packages/d0/a7/a027970c991ca90f24e968999f7d509332daf6b8c3533d68633930aaebac/coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631", size = 212358 }, + { url = "https://files.pythonhosted.org/packages/f2/48/6aaed3651ae83b231556750280682528fea8ac7f1232834573472d83e459/coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f", size = 212620 }, + { url = "https://files.pythonhosted.org/packages/6c/2a/f4b613f3b44d8b9f144847c89151992b2b6b79cbc506dee89ad0c35f209d/coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd", size = 245788 }, + { url = "https://files.pythonhosted.org/packages/04/d2/de4fdc03af5e4e035ef420ed26a703c6ad3d7a07aff2e959eb84e3b19ca8/coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86", size = 243001 }, + { url = "https://files.pythonhosted.org/packages/f5/e8/eed18aa5583b0423ab7f04e34659e51101135c41cd1dcb33ac1d7013a6d6/coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43", size = 244985 }, + { url = "https://files.pythonhosted.org/packages/17/f8/ae9e5cce8885728c934eaa58ebfa8281d488ef2afa81c3dbc8ee9e6d80db/coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1", size = 245152 }, + { url = "https://files.pythonhosted.org/packages/5a/c8/272c01ae792bb3af9b30fac14d71d63371db227980682836ec388e2c57c0/coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751", size = 243123 }, + { url = "https://files.pythonhosted.org/packages/8c/d0/2819a1e3086143c094ab446e3bdf07138527a7b88cb235c488e78150ba7a/coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67", size = 244506 }, + { url = "https://files.pythonhosted.org/packages/8b/4e/9f6117b89152df7b6112f65c7a4ed1f2f5ec8e60c4be8f351d91e7acc848/coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643", size = 214766 }, + { url = "https://files.pythonhosted.org/packages/27/0f/4b59f7c93b52c2c4ce7387c5a4e135e49891bb3b7408dcc98fe44033bbe0/coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a", size = 215568 }, + { url = "https://files.pythonhosted.org/packages/09/1e/9679826336f8c67b9c39a359352882b24a8a7aee48d4c9cad08d38d7510f/coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d", size = 213939 }, + { url = "https://files.pythonhosted.org/packages/bb/5b/5c6b4e7a407359a2e3b27bf9c8a7b658127975def62077d441b93a30dbe8/coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0", size = 213079 }, + { url = "https://files.pythonhosted.org/packages/a2/22/1e2e07279fd2fd97ae26c01cc2186e2258850e9ec125ae87184225662e89/coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d", size = 213299 }, + { url = "https://files.pythonhosted.org/packages/14/c0/4c5125a4b69d66b8c85986d3321520f628756cf524af810baab0790c7647/coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f", size = 256535 }, + { url = "https://files.pythonhosted.org/packages/81/8b/e36a04889dda9960be4263e95e777e7b46f1bb4fc32202612c130a20c4da/coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029", size = 252756 }, + { url = "https://files.pythonhosted.org/packages/98/82/be04eff8083a09a4622ecd0e1f31a2c563dbea3ed848069e7b0445043a70/coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece", size = 254912 }, + { url = "https://files.pythonhosted.org/packages/0f/25/c26610a2c7f018508a5ab958e5b3202d900422cf7cdca7670b6b8ca4e8df/coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683", size = 256144 }, + { url = "https://files.pythonhosted.org/packages/c5/8b/fb9425c4684066c79e863f1e6e7ecebb49e3a64d9f7f7860ef1688c56f4a/coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f", size = 254257 }, + { url = "https://files.pythonhosted.org/packages/93/df/27b882f54157fc1131e0e215b0da3b8d608d9b8ef79a045280118a8f98fe/coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10", size = 255094 }, + { url = "https://files.pythonhosted.org/packages/41/5f/cad1c3dbed8b3ee9e16fa832afe365b4e3eeab1fb6edb65ebbf745eabc92/coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363", size = 215437 }, + { url = "https://files.pythonhosted.org/packages/99/4d/fad293bf081c0e43331ca745ff63673badc20afea2104b431cdd8c278b4c/coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7", size = 216605 }, + { url = "https://files.pythonhosted.org/packages/1f/56/4ee027d5965fc7fc126d7ec1187529cc30cc7d740846e1ecb5e92d31b224/coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c", size = 214392 }, + { url = "https://files.pythonhosted.org/packages/3e/e5/c723545c3fd3204ebde3b4cc4b927dce709d3b6dc577754bb57f63ca4a4a/coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514", size = 204009 }, + { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "45.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712 }, + { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335 }, + { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487 }, + { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922 }, + { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433 }, + { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163 }, + { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687 }, + { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623 }, + { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447 }, + { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830 }, + { url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769 }, + { url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441 }, + { url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836 }, + { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746 }, + { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456 }, + { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495 }, + { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540 }, + { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052 }, + { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024 }, + { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442 }, + { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038 }, + { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964 }, + { url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557 }, + { url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508 }, + { url = "https://files.pythonhosted.org/packages/16/33/b38e9d372afde56906a23839302c19abdac1c505bfb4776c1e4b07c3e145/cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", size = 3580103 }, + { url = "https://files.pythonhosted.org/packages/c4/b9/357f18064ec09d4807800d05a48f92f3b369056a12f995ff79549fbb31f1/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", size = 4143732 }, + { url = "https://files.pythonhosted.org/packages/c4/9c/7f7263b03d5db329093617648b9bd55c953de0b245e64e866e560f9aac07/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", size = 4385424 }, + { url = "https://files.pythonhosted.org/packages/a6/5a/6aa9d8d5073d5acc0e04e95b2860ef2684b2bd2899d8795fc443013e263b/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", size = 4142438 }, + { url = "https://files.pythonhosted.org/packages/42/1c/71c638420f2cdd96d9c2b287fec515faf48679b33a2b583d0f1eda3a3375/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", size = 4384622 }, + { url = "https://files.pythonhosted.org/packages/ef/ab/e3a055c34e97deadbf0d846e189237d3385dca99e1a7e27384c3b2292041/cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", size = 3328911 }, + { url = "https://files.pythonhosted.org/packages/ea/ba/cf442ae99ef363855ed84b39e0fb3c106ac66b7a7703f3c9c9cfe05412cb/cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", size = 3590512 }, + { url = "https://files.pythonhosted.org/packages/28/9a/a7d5bb87d149eb99a5abdc69a41e4e47b8001d767e5f403f78bfaafc7aa7/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", size = 4146899 }, + { url = "https://files.pythonhosted.org/packages/17/11/9361c2c71c42cc5c465cf294c8030e72fb0c87752bacbd7a3675245e3db3/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", size = 4388900 }, + { url = "https://files.pythonhosted.org/packages/c0/76/f95b83359012ee0e670da3e41c164a0c256aeedd81886f878911581d852f/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", size = 4146422 }, + { url = "https://files.pythonhosted.org/packages/09/ad/5429fcc4def93e577a5407988f89cf15305e64920203d4ac14601a9dc876/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", size = 4388475 }, + { url = "https://files.pythonhosted.org/packages/99/49/0ab9774f64555a1b50102757811508f5ace451cf5dc0a2d074a4b9deca6a/cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", size = 3337594 }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "firebase-admin" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachecontrol" }, + { name = "google-api-core", extra = ["grpc"], marker = "platform_python_implementation != 'PyPy'" }, + { name = "google-api-python-client" }, + { name = "google-cloud-firestore", marker = "platform_python_implementation != 'PyPy'" }, + { name = "google-cloud-storage" }, + { name = "httpx", extra = ["http2"] }, + { name = "pyjwt", extra = ["crypto"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/e9/18872c1032905f06d7dd0ae207085a83ca638d40b922accebf5e5bbf735e/firebase_admin-6.9.0.tar.gz", hash = "sha256:06496c3d1380a8f69e3817045b244ce8578d8ad19af3f85c510ac4d8fe0433ca", size = 117059 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/c2/74656081120255c79e613eade6059c43dd648dfad00798a4cec60fcaae34/firebase_admin-6.9.0-py3-none-any.whl", hash = "sha256:75c261c074dcf33a2bcc8366b94ad96255da3fe24079c30220186d489d553ad5", size = 139720 }, +] + +[[package]] +name = "firebase-functions" +source = { editable = "." } +dependencies = [ + { name = "cloudevents" }, + { name = "firebase-admin" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "functions-framework" }, + { name = "google-cloud-firestore" }, + { name = "google-events" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "pyyaml" }, + { name = "typing-extensions" }, +] + +[package.optional-dependencies] +dev = [ + { name = "build" }, + { name = "google-cloud-tasks" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-napoleon" }, + { name = "toml" }, + { name = "tox" }, +] +docs = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-napoleon" }, +] +test = [ + { name = "google-cloud-tasks" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "build", marker = "extra == 'dev'", specifier = ">=1.0.0" }, + { name = "cloudevents", specifier = ">=1.2.0,<2.0.0" }, + { name = "firebase-admin", specifier = ">=6.0.0" }, + { name = "flask", specifier = ">=2.1.2" }, + { name = "flask-cors", specifier = ">=3.0.10" }, + { name = "functions-framework", specifier = ">=3.0.0" }, + { name = "google-cloud-firestore", specifier = ">=2.11.0" }, + { name = "google-cloud-tasks", marker = "extra == 'dev'", specifier = ">=2.13.1" }, + { name = "google-cloud-tasks", marker = "extra == 'test'", specifier = ">=2.13.1" }, + { name = "google-events", specifier = "==0.5.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0.0" }, + { name = "pyjwt", extras = ["crypto"], specifier = ">=2.5.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.1.2" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=7.1.2" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=3.0.0" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=3.0.0" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, + { name = "sphinx", marker = "extra == 'dev'", specifier = ">=6.1.3" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=6.1.3" }, + { name = "sphinxcontrib-napoleon", marker = "extra == 'dev'", specifier = ">=0.7" }, + { name = "sphinxcontrib-napoleon", marker = "extra == 'docs'", specifier = ">=0.7" }, + { name = "toml", marker = "extra == 'dev'", specifier = ">=0.10.2" }, + { name = "tox", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "typing-extensions", specifier = ">=4.4.0" }, +] +provides-extras = ["dev", "test", "docs"] + +[[package]] +name = "flask" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305 }, +] + +[[package]] +name = "flask-cors" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/37/bcfa6c7d5eec777c4c7cf45ce6b27631cebe5230caf88d85eadd63edd37a/flask_cors-6.0.1.tar.gz", hash = "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db", size = 13463 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/f8/01bf35a3afd734345528f98d0353f2a978a476528ad4d7e78b70c4d149dd/flask_cors-6.0.1-py3-none-any.whl", hash = "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c", size = 13244 }, +] + +[[package]] +name = "functions-framework" +version = "3.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudevents" }, + { name = "flask" }, + { name = "gunicorn", marker = "sys_platform != 'win32'" }, + { name = "watchdog" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/1f/367a737a58b53f22113e7f443598a3907d67a5ddf1c45f9d35a6ba642bb0/functions_framework-3.8.3.tar.gz", hash = "sha256:95827698469e3979518d52e32def2f11230465877fef32afd49045013b2a469c", size = 43640 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/52/b0b1fe1b964a9afe6b7da41c88434f09c16bf915cd48b9185e839f96fa6c/functions_framework-3.8.3-py3-none-any.whl", hash = "sha256:fd352272c02ee08b4a3445e234213e43fbdd350365d98e2cce13e1575490bffe", size = 36051 }, +] + +[[package]] +name = "google-api-core" +version = "2.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807 }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-api-python-client" +version = "2.174.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/fd/860fef0cf3edbad828e2ab4d2ddee5dfe8e595b6da748ac6c77e95bc7bef/google_api_python_client-2.174.0.tar.gz", hash = "sha256:9eb7616a820b38a9c12c5486f9b9055385c7feb18b20cbafc5c5a688b14f3515", size = 13127872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/2d/4250b81e8f5309b58650660f403584db6f64067acac74475893a8f33348d/google_api_python_client-2.174.0-py3-none-any.whl", hash = "sha256:f695205ceec97bfaa1590a14282559c4109326c473b07352233a3584cdbf4b89", size = 13650466 }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137 }, +] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253 }, +] + +[[package]] +name = "google-cloud-core" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348 }, +] + +[[package]] +name = "google-cloud-firestore" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/9d/027b9bf61a44422bcdcb00a2acc59152065b1cffa1fc89da62277730973e/google_cloud_firestore-2.21.0.tar.gz", hash = "sha256:0c37faa8506297f827eefc38feb155247a6dcb9a541289631015d125f1b003f8", size = 528159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/03/94755c64a2fb85cba734ac05a4f80096b8c0acfab0508c9d52c57f571687/google_cloud_firestore-2.21.0-py3-none-any.whl", hash = "sha256:bf33ccc38a27afc60748d1f9bb7c46b078d0d39d288636bdfd967611d7b3f17f", size = 368813 }, +] + +[[package]] +name = "google-cloud-storage" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/84/6afc2ffdf31f6247a6bab6ba070e073fb05e0fda56adf59ce52ac591a033/google_cloud_storage-3.1.1.tar.gz", hash = "sha256:f9c8f965cafd1d38509f8e2b070339e0e9e5bf050774653bf36213d4ea6104c0", size = 7668109 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/4f/b922e919f6e1ea5905f1427fadf1a3f56a85e79e2b0037fec182f6b437dd/google_cloud_storage-3.1.1-py3-none-any.whl", hash = "sha256:ba7e6ae2be5a7a08742f001e23ec6a0c17d78c620f63bf8e0e7c2cbdddb407de", size = 175464 }, +] + +[[package]] +name = "google-cloud-tasks" +version = "2.19.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/73/47e178f576c1e2db9cdb389930ca26f1cf1313ca4d29cc3331ed09e3190d/google_cloud_tasks-2.19.3.tar.gz", hash = "sha256:e9da534f3793e0de37f9a3b34ac3647420865ca726c0ac72148c4f721c4894d6", size = 343833 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/8e/fe11607117508c24b56e04c77d0ea21b49ab469e180ff2b25f533dd9a85b/google_cloud_tasks-2.19.3-py3-none-any.whl", hash = "sha256:29bcf8cfa455db0bf5cb3e812e518de6a4e8ff748265898a3a15cfaf35927c7e", size = 289699 }, +] + +[[package]] +name = "google-crc32c" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/69/b1b05cf415df0d86691d6a8b4b7e60ab3a6fb6efb783ee5cd3ed1382bfd3/google_crc32c-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:b07d48faf8292b4db7c3d64ab86f950c2e94e93a11fd47271c28ba458e4a0d76", size = 30467 }, + { url = "https://files.pythonhosted.org/packages/44/3d/92f8928ecd671bd5b071756596971c79d252d09b835cdca5a44177fa87aa/google_crc32c-1.7.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7cc81b3a2fbd932a4313eb53cc7d9dde424088ca3a0337160f35d91826880c1d", size = 30311 }, + { url = "https://files.pythonhosted.org/packages/33/42/c2d15a73df79d45ed6b430b9e801d0bd8e28ac139a9012d7d58af50a385d/google_crc32c-1.7.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c67ca0a1f5b56162951a9dae987988679a7db682d6f97ce0f6381ebf0fbea4c", size = 37889 }, + { url = "https://files.pythonhosted.org/packages/57/ea/ac59c86a3c694afd117bb669bde32aaf17d0de4305d01d706495f09cbf19/google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc5319db92daa516b653600794d5b9f9439a9a121f3e162f94b0e1891c7933cb", size = 33028 }, + { url = "https://files.pythonhosted.org/packages/60/44/87e77e8476767a4a93f6cf271157c6d948eacec63688c093580af13b04be/google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcdf5a64adb747610140572ed18d011896e3b9ae5195f2514b7ff678c80f1603", size = 38026 }, + { url = "https://files.pythonhosted.org/packages/c8/bf/21ac7bb305cd7c1a6de9c52f71db0868e104a5b573a4977cd9d0ff830f82/google_crc32c-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:754561c6c66e89d55754106739e22fdaa93fafa8da7221b29c8b8e8270c6ec8a", size = 33476 }, + { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468 }, + { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313 }, + { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048 }, + { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669 }, + { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476 }, + { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470 }, + { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315 }, + { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180 }, + { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477 }, + { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467 }, + { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309 }, + { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133 }, + { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773 }, + { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475 }, + { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243 }, + { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870 }, + { url = "https://files.pythonhosted.org/packages/0b/43/31e57ce04530794917dfe25243860ec141de9fadf4aa9783dffe7dac7c39/google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8e9afc74168b0b2232fb32dd202c93e46b7d5e4bf03e66ba5dc273bb3559589", size = 28242 }, + { url = "https://files.pythonhosted.org/packages/eb/f3/8b84cd4e0ad111e63e30eb89453f8dd308e3ad36f42305cf8c202461cdf0/google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa8136cc14dd27f34a3221c0f16fd42d8a40e4778273e61a3c19aedaa44daf6b", size = 28049 }, + { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241 }, + { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048 }, +] + +[[package]] +name = "google-events" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/cd/cdea628192f5960688342386a5fbc2d6053234bd8c3232412c435cbb7101/google-events-0.5.0.tar.gz", hash = "sha256:c86511402ee758685ad6b7f3d93c3febb3f60122da71498d6ed4b44f097af49c", size = 127152 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/0f/c5f48d7864a8126dfc45008a7c3203c5665214a40a958a8678666966f3b7/google_events-0.5.0-py3-none-any.whl", hash = "sha256:cb81884a23dea4cec1716cfe2a1c88251653681436c48b2c9dfcae92dc3e4bc3", size = 246513 }, +] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251 }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242 }, +] + +[[package]] +name = "grpcio" +version = "1.73.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/e8/b43b851537da2e2f03fa8be1aef207e5cbfb1a2e014fbb6b40d24c177cd3/grpcio-1.73.1.tar.gz", hash = "sha256:7fce2cd1c0c1116cf3850564ebfc3264fba75d3c74a7414373f1238ea365ef87", size = 12730355 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/51/a5748ab2773d893d099b92653039672f7e26dd35741020972b84d604066f/grpcio-1.73.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:2d70f4ddd0a823436c2624640570ed6097e40935c9194482475fe8e3d9754d55", size = 5365087 }, + { url = "https://files.pythonhosted.org/packages/ae/12/c5ee1a5dfe93dbc2eaa42a219e2bf887250b52e2e2ee5c036c4695f2769c/grpcio-1.73.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:3841a8a5a66830261ab6a3c2a3dc539ed84e4ab019165f77b3eeb9f0ba621f26", size = 10608921 }, + { url = "https://files.pythonhosted.org/packages/c4/6d/b0c6a8120f02b7d15c5accda6bfc43bc92be70ada3af3ba6d8e077c00374/grpcio-1.73.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:628c30f8e77e0258ab788750ec92059fc3d6628590fb4b7cea8c102503623ed7", size = 5803221 }, + { url = "https://files.pythonhosted.org/packages/a6/7a/3c886d9f1c1e416ae81f7f9c7d1995ae72cd64712d29dab74a6bafacb2d2/grpcio-1.73.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67a0468256c9db6d5ecb1fde4bf409d016f42cef649323f0a08a72f352d1358b", size = 6444603 }, + { url = "https://files.pythonhosted.org/packages/42/07/f143a2ff534982c9caa1febcad1c1073cdec732f6ac7545d85555a900a7e/grpcio-1.73.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b84d65bbdebd5926eb5c53b0b9ec3b3f83408a30e4c20c373c5337b4219ec5", size = 6040969 }, + { url = "https://files.pythonhosted.org/packages/fb/0f/523131b7c9196d0718e7b2dac0310eb307b4117bdbfef62382e760f7e8bb/grpcio-1.73.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c54796ca22b8349cc594d18b01099e39f2b7ffb586ad83217655781a350ce4da", size = 6132201 }, + { url = "https://files.pythonhosted.org/packages/ad/18/010a055410eef1d3a7a1e477ec9d93b091ac664ad93e9c5f56d6cc04bdee/grpcio-1.73.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:75fc8e543962ece2f7ecd32ada2d44c0c8570ae73ec92869f9af8b944863116d", size = 6774718 }, + { url = "https://files.pythonhosted.org/packages/16/11/452bfc1ab39d8ee748837ab8ee56beeae0290861052948785c2c445fb44b/grpcio-1.73.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6a6037891cd2b1dd1406b388660522e1565ed340b1fea2955b0234bdd941a862", size = 6304362 }, + { url = "https://files.pythonhosted.org/packages/1e/1c/c75ceee626465721e5cb040cf4b271eff817aa97388948660884cb7adffa/grpcio-1.73.1-cp310-cp310-win32.whl", hash = "sha256:cce7265b9617168c2d08ae570fcc2af4eaf72e84f8c710ca657cc546115263af", size = 3679036 }, + { url = "https://files.pythonhosted.org/packages/62/2e/42cb31b6cbd671a7b3dbd97ef33f59088cf60e3cf2141368282e26fafe79/grpcio-1.73.1-cp310-cp310-win_amd64.whl", hash = "sha256:6a2b372e65fad38842050943f42ce8fee00c6f2e8ea4f7754ba7478d26a356ee", size = 4340208 }, + { url = "https://files.pythonhosted.org/packages/e4/41/921565815e871d84043e73e2c0e748f0318dab6fa9be872cd042778f14a9/grpcio-1.73.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:ba2cea9f7ae4bc21f42015f0ec98f69ae4179848ad744b210e7685112fa507a1", size = 5363853 }, + { url = "https://files.pythonhosted.org/packages/b0/cc/9c51109c71d068e4d474becf5f5d43c9d63038cec1b74112978000fa72f4/grpcio-1.73.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d74c3f4f37b79e746271aa6cdb3a1d7e4432aea38735542b23adcabaaee0c097", size = 10621476 }, + { url = "https://files.pythonhosted.org/packages/8f/d3/33d738a06f6dbd4943f4d377468f8299941a7c8c6ac8a385e4cef4dd3c93/grpcio-1.73.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5b9b1805a7d61c9e90541cbe8dfe0a593dfc8c5c3a43fe623701b6a01b01d710", size = 5807903 }, + { url = "https://files.pythonhosted.org/packages/5d/47/36deacd3c967b74e0265f4c608983e897d8bb3254b920f8eafdf60e4ad7e/grpcio-1.73.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3215f69a0670a8cfa2ab53236d9e8026bfb7ead5d4baabe7d7dc11d30fda967", size = 6448172 }, + { url = "https://files.pythonhosted.org/packages/0e/64/12d6dc446021684ee1428ea56a3f3712048a18beeadbdefa06e6f8814a6e/grpcio-1.73.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc5eccfd9577a5dc7d5612b2ba90cca4ad14c6d949216c68585fdec9848befb1", size = 6044226 }, + { url = "https://files.pythonhosted.org/packages/72/4b/6bae2d88a006000f1152d2c9c10ffd41d0131ca1198e0b661101c2e30ab9/grpcio-1.73.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc7d7fd520614fce2e6455ba89791458020a39716951c7c07694f9dbae28e9c0", size = 6135690 }, + { url = "https://files.pythonhosted.org/packages/38/64/02c83b5076510784d1305025e93e0d78f53bb6a0213c8c84cfe8a00c5c48/grpcio-1.73.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:105492124828911f85127e4825d1c1234b032cb9d238567876b5515d01151379", size = 6775867 }, + { url = "https://files.pythonhosted.org/packages/42/72/a13ff7ba6c68ccffa35dacdc06373a76c0008fd75777cba84d7491956620/grpcio-1.73.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:610e19b04f452ba6f402ac9aa94eb3d21fbc94553368008af634812c4a85a99e", size = 6308380 }, + { url = "https://files.pythonhosted.org/packages/65/ae/d29d948021faa0070ec33245c1ae354e2aefabd97e6a9a7b6dcf0fb8ef6b/grpcio-1.73.1-cp311-cp311-win32.whl", hash = "sha256:d60588ab6ba0ac753761ee0e5b30a29398306401bfbceffe7d68ebb21193f9d4", size = 3679139 }, + { url = "https://files.pythonhosted.org/packages/af/66/e1bbb0c95ea222947f0829b3db7692c59b59bcc531df84442e413fa983d9/grpcio-1.73.1-cp311-cp311-win_amd64.whl", hash = "sha256:6957025a4608bb0a5ff42abd75bfbb2ed99eda29d5992ef31d691ab54b753643", size = 4342558 }, + { url = "https://files.pythonhosted.org/packages/b8/41/456caf570c55d5ac26f4c1f2db1f2ac1467d5bf3bcd660cba3e0a25b195f/grpcio-1.73.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:921b25618b084e75d424a9f8e6403bfeb7abef074bb6c3174701e0f2542debcf", size = 5334621 }, + { url = "https://files.pythonhosted.org/packages/2a/c2/9a15e179e49f235bb5e63b01590658c03747a43c9775e20c4e13ca04f4c4/grpcio-1.73.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:277b426a0ed341e8447fbf6c1d6b68c952adddf585ea4685aa563de0f03df887", size = 10601131 }, + { url = "https://files.pythonhosted.org/packages/0c/1d/1d39e90ef6348a0964caa7c5c4d05f3bae2c51ab429eb7d2e21198ac9b6d/grpcio-1.73.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:96c112333309493c10e118d92f04594f9055774757f5d101b39f8150f8c25582", size = 5759268 }, + { url = "https://files.pythonhosted.org/packages/8a/2b/2dfe9ae43de75616177bc576df4c36d6401e0959833b2e5b2d58d50c1f6b/grpcio-1.73.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f48e862aed925ae987eb7084409a80985de75243389dc9d9c271dd711e589918", size = 6409791 }, + { url = "https://files.pythonhosted.org/packages/6e/66/e8fe779b23b5a26d1b6949e5c70bc0a5fd08f61a6ec5ac7760d589229511/grpcio-1.73.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a6c2cce218e28f5040429835fa34a29319071079e3169f9543c3fbeff166d2", size = 6003728 }, + { url = "https://files.pythonhosted.org/packages/a9/39/57a18fcef567784108c4fc3f5441cb9938ae5a51378505aafe81e8e15ecc/grpcio-1.73.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65b0458a10b100d815a8426b1442bd17001fdb77ea13665b2f7dc9e8587fdc6b", size = 6103364 }, + { url = "https://files.pythonhosted.org/packages/c5/46/28919d2aa038712fc399d02fa83e998abd8c1f46c2680c5689deca06d1b2/grpcio-1.73.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0a9f3ea8dce9eae9d7cb36827200133a72b37a63896e0e61a9d5ec7d61a59ab1", size = 6749194 }, + { url = "https://files.pythonhosted.org/packages/3d/56/3898526f1fad588c5d19a29ea0a3a4996fb4fa7d7c02dc1be0c9fd188b62/grpcio-1.73.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de18769aea47f18e782bf6819a37c1c528914bfd5683b8782b9da356506190c8", size = 6283902 }, + { url = "https://files.pythonhosted.org/packages/dc/64/18b77b89c5870d8ea91818feb0c3ffb5b31b48d1b0ee3e0f0d539730fea3/grpcio-1.73.1-cp312-cp312-win32.whl", hash = "sha256:24e06a5319e33041e322d32c62b1e728f18ab8c9dbc91729a3d9f9e3ed336642", size = 3668687 }, + { url = "https://files.pythonhosted.org/packages/3c/52/302448ca6e52f2a77166b2e2ed75f5d08feca4f2145faf75cb768cccb25b/grpcio-1.73.1-cp312-cp312-win_amd64.whl", hash = "sha256:303c8135d8ab176f8038c14cc10d698ae1db9c480f2b2823f7a987aa2a4c5646", size = 4334887 }, + { url = "https://files.pythonhosted.org/packages/37/bf/4ca20d1acbefabcaba633ab17f4244cbbe8eca877df01517207bd6655914/grpcio-1.73.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b310824ab5092cf74750ebd8a8a8981c1810cb2b363210e70d06ef37ad80d4f9", size = 5335615 }, + { url = "https://files.pythonhosted.org/packages/75/ed/45c345f284abec5d4f6d77cbca9c52c39b554397eb7de7d2fcf440bcd049/grpcio-1.73.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:8f5a6df3fba31a3485096ac85b2e34b9666ffb0590df0cd044f58694e6a1f6b5", size = 10595497 }, + { url = "https://files.pythonhosted.org/packages/a4/75/bff2c2728018f546d812b755455014bc718f8cdcbf5c84f1f6e5494443a8/grpcio-1.73.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:052e28fe9c41357da42250a91926a3e2f74c046575c070b69659467ca5aa976b", size = 5765321 }, + { url = "https://files.pythonhosted.org/packages/70/3b/14e43158d3b81a38251b1d231dfb45a9b492d872102a919fbf7ba4ac20cd/grpcio-1.73.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c0bf15f629b1497436596b1cbddddfa3234273490229ca29561209778ebe182", size = 6415436 }, + { url = "https://files.pythonhosted.org/packages/e5/3f/81d9650ca40b54338336fd360f36773be8cb6c07c036e751d8996eb96598/grpcio-1.73.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854", size = 6007012 }, + { url = "https://files.pythonhosted.org/packages/55/f4/59edf5af68d684d0f4f7ad9462a418ac517201c238551529098c9aa28cb0/grpcio-1.73.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d958c31cc91ab050bd8a91355480b8e0683e21176522bacea225ce51163f2", size = 6105209 }, + { url = "https://files.pythonhosted.org/packages/e4/a8/700d034d5d0786a5ba14bfa9ce974ed4c976936c2748c2bd87aa50f69b36/grpcio-1.73.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f43ffb3bd415c57224c7427bfb9e6c46a0b6e998754bfa0d00f408e1873dcbb5", size = 6753655 }, + { url = "https://files.pythonhosted.org/packages/1f/29/efbd4ac837c23bc48e34bbaf32bd429f0dc9ad7f80721cdb4622144c118c/grpcio-1.73.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:686231cdd03a8a8055f798b2b54b19428cdf18fa1549bee92249b43607c42668", size = 6287288 }, + { url = "https://files.pythonhosted.org/packages/d8/61/c6045d2ce16624bbe18b5d169c1a5ce4d6c3a47bc9d0e5c4fa6a50ed1239/grpcio-1.73.1-cp313-cp313-win32.whl", hash = "sha256:89018866a096e2ce21e05eabed1567479713ebe57b1db7cbb0f1e3b896793ba4", size = 3668151 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/77ac689216daee10de318db5aa1b88d159432dc76a130948a56b3aa671a2/grpcio-1.73.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a68f8c9966b94dff693670a5cf2b54888a48a5011c5d9ce2295a1a1465ee84f", size = 4335747 }, +] + +[[package]] +name = "grpcio-status" +version = "1.73.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/59/9350a13804f2e407d76b3962c548e023639fc1545056e342c6bad0d4fd30/grpcio_status-1.73.1.tar.gz", hash = "sha256:928f49ccf9688db5f20cd9e45c4578a1d01ccca29aeaabf066f2ac76aa886668", size = 13664 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/50/ee32e6073e2c3a4457be168e2bbf84d02ad9d2c18c4a578a641480c293d4/grpcio_status-1.73.1-py3-none-any.whl", hash = "sha256:538595c32a6c819c32b46a621a51e9ae4ffcd7e7e1bce35f728ef3447e9809b6", size = 14422 }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httplib2" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + +[[package]] +name = "identify" +version = "2.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "msgpack" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/52/f30da112c1dc92cf64f57d08a273ac771e7b29dea10b4b30369b2d7e8546/msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", size = 81799 }, + { url = "https://files.pythonhosted.org/packages/e4/35/7bfc0def2f04ab4145f7f108e3563f9b4abae4ab0ed78a61f350518cc4d2/msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", size = 78278 }, + { url = "https://files.pythonhosted.org/packages/e8/c5/df5d6c1c39856bc55f800bf82778fd4c11370667f9b9e9d51b2f5da88f20/msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", size = 402805 }, + { url = "https://files.pythonhosted.org/packages/20/8e/0bb8c977efecfe6ea7116e2ed73a78a8d32a947f94d272586cf02a9757db/msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", size = 408642 }, + { url = "https://files.pythonhosted.org/packages/59/a1/731d52c1aeec52006be6d1f8027c49fdc2cfc3ab7cbe7c28335b2910d7b6/msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", size = 395143 }, + { url = "https://files.pythonhosted.org/packages/2b/92/b42911c52cda2ba67a6418ffa7d08969edf2e760b09015593c8a8a27a97d/msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", size = 395986 }, + { url = "https://files.pythonhosted.org/packages/61/dc/8ae165337e70118d4dab651b8b562dd5066dd1e6dd57b038f32ebc3e2f07/msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75", size = 402682 }, + { url = "https://files.pythonhosted.org/packages/58/27/555851cb98dcbd6ce041df1eacb25ac30646575e9cd125681aa2f4b1b6f1/msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", size = 406368 }, + { url = "https://files.pythonhosted.org/packages/d4/64/39a26add4ce16f24e99eabb9005e44c663db00e3fce17d4ae1ae9d61df99/msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", size = 65004 }, + { url = "https://files.pythonhosted.org/packages/7d/18/73dfa3e9d5d7450d39debde5b0d848139f7de23bd637a4506e36c9800fd6/msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", size = 71548 }, + { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728 }, + { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279 }, + { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859 }, + { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975 }, + { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528 }, + { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338 }, + { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658 }, + { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124 }, + { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016 }, + { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267 }, + { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359 }, + { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172 }, + { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013 }, + { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905 }, + { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336 }, + { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485 }, + { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182 }, + { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883 }, + { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406 }, + { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558 }, + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677 }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603 }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504 }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749 }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458 }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976 }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607 }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172 }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347 }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341 }, +] + +[[package]] +name = "mypy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644 }, + { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033 }, + { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645 }, + { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986 }, + { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632 }, + { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391 }, + { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557 }, + { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921 }, + { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887 }, + { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658 }, + { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486 }, + { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482 }, + { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 }, + { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 }, + { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 }, + { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 }, + { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 }, + { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 }, + { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 }, + { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 }, + { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 }, + { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 }, + { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 }, + { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 }, + { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pockets" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/8e/0601097cfcce2e8c2297db5080e9719f549c2bd4b94420ddc8d3f848bbca/pockets-0.9.1.tar.gz", hash = "sha256:9320f1a3c6f7a9133fe3b571f283bcf3353cd70249025ae8d618e40e9f7e92b3", size = 24993 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/2f/a4583c70fbd8cd04910e2884bcc2bdd670e884061f7b4d70bc13e632a993/pockets-0.9.1-py2.py3-none-any.whl", hash = "sha256:68597934193c08a08eb2bf6a1d85593f627c22f9b065cc727a4f03f669d96d86", size = 26263 }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163 }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603 }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283 }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604 }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115 }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070 }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724 }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/fd/437901c891f58a7b9096511750247535e891d2d5a5a6eefbc9386a2b41d5/pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", size = 22710 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e6/c293c06695d4a3ab0260ef124a74ebadba5f4c511ce3a4259e976902c00b/pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948", size = 13158 }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +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/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 }, +] + +[[package]] +name = "ruff" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649 }, + { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201 }, + { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769 }, + { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902 }, + { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002 }, + { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522 }, + { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264 }, + { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882 }, + { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941 }, + { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887 }, + { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742 }, + { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909 }, + { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005 }, + { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579 }, + { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495 }, + { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485 }, + { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274 }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-napoleon" +version = "0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pockets" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/eb/ad89500f4cee83187596e07f43ad561f293e8e6e96996005c3319653b89f/sphinxcontrib-napoleon-0.7.tar.gz", hash = "sha256:407382beed396e9f2d7f3043fad6afda95719204a1e1a231ac865f40abcbfcf8", size = 21232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/f2/6b7627dfe7b4e418e295e254bb15c3a6455f11f8c0ad0d43113f678049c3/sphinxcontrib_napoleon-0.7-py2.py3-none-any.whl", hash = "sha256:711e41a3974bdf110a484aec4c1a556799eb0b3f3b897521a018ad7e2db13fef", size = 17151 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tox" +version = "4.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/b7/19c01717747076f63c54d871ada081cd711a7c9a7572f2225675c3858b94/tox-4.27.0.tar.gz", hash = "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57", size = 198351 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/3a/30889167f41ecaffb957ec4409e1cbc1d5d558a5bbbdfb734a5b9911930f/tox-4.27.0-py3-none-any.whl", hash = "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20", size = 173441 }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 }, +] + +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488 }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, +] + +[[package]] +name = "virtualenv" +version = "20.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, +] From 1a248893d7de764e101461b7f5373ccd73ec23f2 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:06:51 -0700 Subject: [PATCH 3/9] fix: add formatting commit hash to .git-blame-ignore-revs --- .git-blame-ignore-revs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 8a0641d..968f6d4 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -3,4 +3,4 @@ # Configure with: git config blame.ignoreRevsFile .git-blame-ignore-revs # Switch to ruff formatting -# TODO: Add commit hash here after formatting commit \ No newline at end of file +fba8bda \ No newline at end of file From 48e84b14ac9ed681e9be524f524de3e4a420ee21 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:09:57 -0700 Subject: [PATCH 4/9] fix: remove unrelated documentation files from PR --- async.md | 248 ----------------------------------------------- modernization.md | 160 ------------------------------ 2 files changed, 408 deletions(-) delete mode 100644 async.md delete mode 100644 modernization.md diff --git a/async.md b/async.md deleted file mode 100644 index 67e62f0..0000000 --- a/async.md +++ /dev/null @@ -1,248 +0,0 @@ -# Async Support for Firebase Functions Python - -## Overview - -This document outlines the design and implementation plan for adding async function support to firebase-functions-python. The goal is to leverage the new async capabilities in functions-framework while maintaining full backward compatibility with existing sync functions. - -## Background - -Functions-framework recently added async support via the `--asgi` flag, allowing async functions to be defined like: - -```python -import functions_framework.aio - -@functions_framework.aio.http -async def hello_async(request): # Starlette.Request - await asyncio.sleep(1) - return "Hello, async world!" -``` - -## Design Goals - -1. **No code duplication** - Reuse existing decorators and logic -2. **Backward compatibility** - All existing sync functions must continue to work -3. **Unified API** - Users shouldn't need different decorators for sync vs async -4. **Type safety** - Proper typing for both sync and async cases -5. **Automatic detection** - The system should automatically detect and handle async functions -6. **Universal support** - Async should work for ALL function types, not just HTTP - -## Function Types to Support - -Firebase Functions Python supports multiple trigger types that all need async support: - -### 1. HTTP Functions -- `@https_fn.on_request()` - Raw HTTP requests -- `@https_fn.on_call()` - Callable functions with auth/validation - -### 2. Firestore Functions -- `@firestore_fn.on_document_created()` -- `@firestore_fn.on_document_updated()` -- `@firestore_fn.on_document_deleted()` -- `@firestore_fn.on_document_written()` - -### 3. Realtime Database Functions -- `@db_fn.on_value_created()` -- `@db_fn.on_value_updated()` -- `@db_fn.on_value_deleted()` -- `@db_fn.on_value_written()` - -### 4. Cloud Storage Functions -- `@storage_fn.on_object_archived()` -- `@storage_fn.on_object_deleted()` -- `@storage_fn.on_object_finalized()` -- `@storage_fn.on_object_metadata_updated()` - -### 5. Pub/Sub Functions -- `@pubsub_fn.on_message_published()` - -### 6. Scheduler Functions -- `@scheduler_fn.on_schedule()` - -### 7. Task Queue Functions -- `@tasks_fn.on_task_dispatched()` - -### 8. EventArc Functions -- `@eventarc_fn.on_custom_event_published()` - -### 9. Remote Config Functions -- `@remote_config_fn.on_config_updated()` - -### 10. Test Lab Functions -- `@test_lab_fn.on_test_matrix_completed()` - -### 11. Alerts Functions -- Various alert triggers for billing, crashlytics, performance, etc. - -### 12. Identity Functions -- `@identity_fn.before_user_created()` -- `@identity_fn.before_user_signed_in()` - -## Implementation Strategy - -### Phase 1: Core Infrastructure - -#### 1.1 Async Detection Mechanism -- Add utility function to detect if a function is async using `inspect.iscoroutinefunction()` -- This detection should happen at decoration time - -#### 1.2 Metadata Storage -- Extend the `__firebase_endpoint__` attribute to include runtime mode information -- Add a field to `ManifestEndpoint` to indicate async functions: - ```python - @dataclasses.dataclass(frozen=True) - class ManifestEndpoint: - # ... existing fields ... - runtime_mode: Literal["sync", "async"] | None = "sync" - ``` - -#### 1.3 Type System Updates -- Create type unions to handle both sync and async cases -- For HTTP functions: - - Sync: `flask.Request` and `flask.Response` - - Async: `starlette.requests.Request` and response types -- For event functions: - - Both sync and async will receive the same event objects - - The difference is whether the handler is async - -### Phase 2: Decorator Updates - -#### 2.1 Universal Decorator Pattern -Each decorator should follow this pattern: - -```python -def on_some_event(**kwargs): - def decorator(func): - is_async = inspect.iscoroutinefunction(func) - - if is_async: - # Set up async wrapper - @functools.wraps(func) - async def async_wrapper(*args, **kwargs): - # Any necessary async setup - return await func(*args, **kwargs) - - wrapped = async_wrapper - runtime_mode = "async" - else: - # Use existing sync wrapper - wrapped = existing_sync_wrapper(func) - runtime_mode = "sync" - - # Set metadata - endpoint = create_endpoint( - # ... existing endpoint config ... - runtime_mode=runtime_mode - ) - _util.set_func_endpoint_attr(wrapped, endpoint) - - return wrapped - - return decorator -``` - -#### 2.2 HTTP Functions Special Handling -HTTP functions need special care because the request type changes: -- Sync: `flask.Request` -- Async: `starlette.requests.Request` - -We'll need to handle this in the type system and potentially in request processing. - -### Phase 3: Manifest and Deployment - -#### 3.1 Manifest Generation -- Update `serving.py` to include runtime mode in the manifest -- The functions.yaml should indicate which functions need async runtime - -#### 3.2 Firebase CLI Integration -- The CLI needs to read the runtime mode from the manifest -- When deploying async functions, it should: - - Set appropriate environment variables - - Pass the `--asgi` flag to functions-framework - - Potentially use different container configurations - -### Phase 4: Testing and Validation - -#### 4.1 Test Coverage -- Add async versions of existing tests -- Test mixed deployments (both sync and async functions) -- Verify proper error handling in async contexts -- Test timeout behavior for async functions - -#### 4.2 Example Updates -- Update examples to show async usage -- Create migration guide for converting sync to async - -## Example Usage - -### HTTP Functions -```python -# Sync (existing) -@https_fn.on_request() -def sync_http(request: Request) -> Response: - return Response("Hello sync") - -# Async (new) -@https_fn.on_request() -async def async_http(request) -> Response: # Will be Starlette Request - result = await some_async_api_call() - return Response(f"Hello async: {result}") -``` - -### Firestore Functions -```python -# Sync (existing) -@firestore_fn.on_document_created(document="users/{userId}") -def sync_user_created(event: Event[DocumentSnapshot]) -> None: - print(f"User created: {event.data.id}") - -# Async (new) -@firestore_fn.on_document_created(document="users/{userId}") -async def async_user_created(event: Event[DocumentSnapshot]) -> None: - await send_welcome_email(event.data.get("email")) - await update_analytics(event.data.id) -``` - -### Pub/Sub Functions -```python -# Async (new) -@pubsub_fn.on_message_published(topic="process-queue") -async def async_process_message(event: CloudEvent[MessagePublishedData]) -> None: - message = event.data.message - await process_job(message.data) -``` - -## Benefits - -1. **Performance**: Async functions can handle I/O-bound operations more efficiently -2. **Scalability**: Better resource utilization for functions that make external API calls -3. **Modern Python**: Aligns with Python's async/await ecosystem -4. **Flexibility**: Users can choose sync or async based on their needs - -## Considerations - -1. **Cold Start**: Need to verify async functions don't increase cold start times -2. **Memory Usage**: Monitor if async runtime uses more memory -3. **Debugging**: Ensure stack traces and error messages are clear for async functions -4. **Timeouts**: Verify timeout behavior works correctly with async functions - -## Migration Path - -1. Start with HTTP functions as proof of concept -2. Extend to event-triggered functions -3. Update documentation and examples -4. Release as minor version update (backward compatible) - -## Open Questions - -1. Should we support both Flask and Starlette response types for async HTTP functions? -2. How should we handle async context managers and cleanup? -3. Should we provide async versions of Firebase Admin SDK operations? -4. What's the best way to handle errors in async functions? - -## Next Steps - -1. Prototype async support for HTTP functions -2. Test with functions-framework in ASGI mode -3. Design type system for handling both sync and async -4. Update manifest generation -5. Coordinate with Firebase CLI team for deployment support \ No newline at end of file diff --git a/modernization.md b/modernization.md deleted file mode 100644 index 830c62e..0000000 --- a/modernization.md +++ /dev/null @@ -1,160 +0,0 @@ -# Firebase Functions Python Repository Modernization Plan - -## Overview -Modernize the Firebase Functions Python SDK to use contemporary Python tooling while maintaining compatibility with existing release processes and improving developer experience. - -## 1. **Migrate to Modern pyproject.toml Packaging** -- Convert setup.py to a modern pyproject.toml using setuptools build backend -- Keep dynamic version reading from `__init__.py` (required by release process) -- Preserve all metadata, classifiers, and package configuration -- Use PEP 621 standard for metadata -- Configure build backend: `[build-system]` with setuptools -- Remove setup.py entirely after migration - -## 2. **Adopt uv for Development** -- Replace pip/venv with uv for faster dependency management -- Define all dependencies in pyproject.toml: - ```toml - [project.dependencies] # Runtime dependencies - [project.optional-dependencies] - dev = [...] # All dev dependencies - test = [...] # Test-only dependencies - docs = [...] # Documentation dependencies - ``` -- Generate uv.lock for reproducible builds -- Add .python-version file set to 3.10 - -## 3. **Replace pylint/YAPF with ruff** -- Configure ruff to match current Google style guide: - - Line length: 100 - - Indentation: 4 spaces - - Enable equivalent pylint rules -- Remove .pylintrc and yapf configuration -- Add comprehensive ruff configuration in pyproject.toml -- Set target-version = "py310" for compatibility checks - -## 4. **Consider tox for Testing** -Yes, add tox for: -- Testing across Python 3.10, 3.11, 3.12 -- Running different test environments (unit, integration) -- Linting/formatting environments -- Documentation building -- Type checking -- Package building verification - -Benefits: -- Ensures compatibility across Python versions -- Isolated test environments -- Reproducible testing -- Can be used both locally and in CI - -## 5. **Add Modern Development Tools** -### Pre-commit hooks: -- ruff format and lint checks -- mypy type checking -- Check merge conflicts -- Trailing whitespace -- End of file fixer -- Check added large files -- Validate pyproject.toml - -### Development scripts: -Add convenience commands via: -- justfile for task automation -- Or pyproject.toml scripts section -- Common tasks: test, lint, format, typecheck, docs - -## 6. **Update CI/CD Workflows** - -### CI Workflow Updates: -- Install uv in GitHub Actions -- Use `uv sync` instead of `pip install` -- Replace pylint with `ruff check` -- Replace yapf with `ruff format --check` -- Use uv's built-in venv management -- Add dependency caching for uv - -### Release Workflow Updates: -**Critical**: The release process depends on: -1. Reading version from `src/firebase_functions/__init__.py` -2. Building with `python setup.py bdist_wheel sdist` -3. Specific artifact naming patterns - -**Changes needed**: -- Replace `python setup.py bdist_wheel sdist` with `python -m build` -- Ensure pyproject.toml maintains dynamic version reading -- Keep artifact names identical (firebase_functions-{version}-py3-none-any.whl) -- No changes needed to publish_preflight_check.sh - -## 7. **Migration Strategy** - -### Phase 1: Add new tooling (non-breaking) -1. Create comprehensive pyproject.toml -2. Install and configure uv -3. Set up ruff configuration -4. Add tox.ini -5. Configure pre-commit - -### Phase 2: Update workflows -1. Update local development docs -2. Modify CI to use new tools -3. Update release workflow for modern build - -### Phase 3: Remove old tooling -1. Remove setup.py -2. Delete .pylintrc -3. Remove yapf config from current pyproject.toml -4. Clean up old dependencies - -## 8. **File Structure Changes** - -### New files: -- `pyproject.toml` (expanded with all config) -- `uv.lock` -- `.python-version` -- `tox.ini` -- `.pre-commit-config.yaml` -- `justfile` (optional) - -### Modified files: -- `.github/workflows/ci.yaml` -- `.github/workflows/release.yaml` -- `.gitignore` (add uv directories) - -### Removed files: -- `setup.py` -- `.pylintrc` -- `pytest.ini` (migrate to pyproject.toml) - -## 9. **Compatibility Considerations** - -- Maintain Python 3.10+ requirement -- Keep all existing package metadata -- Preserve version reading mechanism -- Ensure built artifacts remain identical -- No changes to API or functionality -- Support existing installation methods - -## 10. **Benefits Summary** - -- **10-100x faster**: uv for dependency management -- **Faster linting**: ruff vs pylint/yapf -- **Modern standards**: PEP 517/518/621 compliance -- **Better reproducibility**: Lock files -- **Improved DX**: Pre-commit hooks, better tooling -- **Multi-version testing**: tox integration -- **Simpler configuration**: Everything in pyproject.toml - -## Implementation Steps - -1. Save this plan as modernization.md -2. Create new pyproject.toml with all configurations -3. Set up uv and generate lock file -4. Configure and test ruff -5. Add tox configuration -6. Set up pre-commit hooks -7. Update CI/CD workflows -8. Remove old configuration files -9. Update documentation - -This plan modernizes the tooling while maintaining full compatibility with the existing release process and Firebase ecosystem requirements. \ No newline at end of file From 23df20820e464cd8e3529fc82b8e1ad0a714879d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:11:17 -0700 Subject: [PATCH 5/9] fix: remove uv.lock from PR --- uv.lock | 1812 ------------------------------------------------------- 1 file changed, 1812 deletions(-) delete mode 100644 uv.lock diff --git a/uv.lock b/uv.lock deleted file mode 100644 index cf12447..0000000 --- a/uv.lock +++ /dev/null @@ -1,1812 +0,0 @@ -version = 1 -revision = 1 -requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version >= '3.11' and python_full_version < '3.13'", - "python_full_version < '3.11'", -] - -[[package]] -name = "alabaster" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, -] - -[[package]] -name = "anyio" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, -] - -[[package]] -name = "babel" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, -] - -[[package]] -name = "blinker" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, -] - -[[package]] -name = "build" -version = "1.2.2.post1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950 }, -] - -[[package]] -name = "cachecontrol" -version = "0.14.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msgpack" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/3a/0cbeb04ea57d2493f3ec5a069a117ab467f85e4a10017c6d854ddcbff104/cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11", size = 28985 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/4c/800b0607b00b3fd20f1087f80ab53d6b4d005515b0f773e4831e37cfa83f/cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae", size = 21802 }, -] - -[[package]] -name = "cachetools" -version = "5.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, -] - -[[package]] -name = "certifi" -version = "2025.6.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650 }, -] - -[[package]] -name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, -] - -[[package]] -name = "chardet" -version = "5.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818 }, - { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649 }, - { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045 }, - { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356 }, - { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471 }, - { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317 }, - { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368 }, - { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491 }, - { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695 }, - { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849 }, - { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091 }, - { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445 }, - { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782 }, - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, -] - -[[package]] -name = "click" -version = "8.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, -] - -[[package]] -name = "cloudevents" -version = "1.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecation" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/aa/804bdb5f2f021fcc887eeabfa24bad0ffd4b150f60850ae88faa51d393a5/cloudevents-1.12.0.tar.gz", hash = "sha256:ebd5544ceb58c8378a0787b657a2ae895e929b80a82d6675cba63f0e8c5539e0", size = 34494 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/b6/4e29b74bb40daa7580310a5ff0df5f121a08ce98340e01a960b668468aab/cloudevents-1.12.0-py3-none-any.whl", hash = "sha256:49196267f5f963d87ae156f93fc0fa32f4af69485f2c8e62e0db8b0b4b8b8921", size = 55762 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "coverage" -version = "7.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/78/1c1c5ec58f16817c09cbacb39783c3655d54a221b6552f47ff5ac9297603/coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca", size = 212028 }, - { url = "https://files.pythonhosted.org/packages/98/db/e91b9076f3a888e3b4ad7972ea3842297a52cc52e73fd1e529856e473510/coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509", size = 212420 }, - { url = "https://files.pythonhosted.org/packages/0e/d0/2b3733412954576b0aea0a16c3b6b8fbe95eb975d8bfa10b07359ead4252/coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b", size = 241529 }, - { url = "https://files.pythonhosted.org/packages/b3/00/5e2e5ae2e750a872226a68e984d4d3f3563cb01d1afb449a17aa819bc2c4/coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3", size = 239403 }, - { url = "https://files.pythonhosted.org/packages/37/3b/a2c27736035156b0a7c20683afe7df498480c0dfdf503b8c878a21b6d7fb/coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3", size = 240548 }, - { url = "https://files.pythonhosted.org/packages/98/f5/13d5fc074c3c0e0dc80422d9535814abf190f1254d7c3451590dc4f8b18c/coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5", size = 240459 }, - { url = "https://files.pythonhosted.org/packages/36/24/24b9676ea06102df824c4a56ffd13dc9da7904478db519efa877d16527d5/coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187", size = 239128 }, - { url = "https://files.pythonhosted.org/packages/be/05/242b7a7d491b369ac5fee7908a6e5ba42b3030450f3ad62c645b40c23e0e/coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce", size = 239402 }, - { url = "https://files.pythonhosted.org/packages/73/e0/4de7f87192fa65c9c8fbaeb75507e124f82396b71de1797da5602898be32/coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70", size = 214518 }, - { url = "https://files.pythonhosted.org/packages/d5/ab/5e4e2fe458907d2a65fab62c773671cfc5ac704f1e7a9ddd91996f66e3c2/coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe", size = 215436 }, - { url = "https://files.pythonhosted.org/packages/60/34/fa69372a07d0903a78ac103422ad34db72281c9fc625eba94ac1185da66f/coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582", size = 212146 }, - { url = "https://files.pythonhosted.org/packages/27/f0/da1894915d2767f093f081c42afeba18e760f12fdd7a2f4acbe00564d767/coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86", size = 212536 }, - { url = "https://files.pythonhosted.org/packages/10/d5/3fc33b06e41e390f88eef111226a24e4504d216ab8e5d1a7089aa5a3c87a/coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed", size = 245092 }, - { url = "https://files.pythonhosted.org/packages/0a/39/7aa901c14977aba637b78e95800edf77f29f5a380d29768c5b66f258305b/coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d", size = 242806 }, - { url = "https://files.pythonhosted.org/packages/43/fc/30e5cfeaf560b1fc1989227adedc11019ce4bb7cce59d65db34fe0c2d963/coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338", size = 244610 }, - { url = "https://files.pythonhosted.org/packages/bf/15/cca62b13f39650bc87b2b92bb03bce7f0e79dd0bf2c7529e9fc7393e4d60/coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875", size = 244257 }, - { url = "https://files.pythonhosted.org/packages/cd/1a/c0f2abe92c29e1464dbd0ff9d56cb6c88ae2b9e21becdb38bea31fcb2f6c/coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250", size = 242309 }, - { url = "https://files.pythonhosted.org/packages/57/8d/c6fd70848bd9bf88fa90df2af5636589a8126d2170f3aade21ed53f2b67a/coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c", size = 242898 }, - { url = "https://files.pythonhosted.org/packages/c2/9e/6ca46c7bff4675f09a66fe2797cd1ad6a24f14c9c7c3b3ebe0470a6e30b8/coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32", size = 214561 }, - { url = "https://files.pythonhosted.org/packages/a1/30/166978c6302010742dabcdc425fa0f938fa5a800908e39aff37a7a876a13/coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125", size = 215493 }, - { url = "https://files.pythonhosted.org/packages/60/07/a6d2342cd80a5be9f0eeab115bc5ebb3917b4a64c2953534273cf9bc7ae6/coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e", size = 213869 }, - { url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336 }, - { url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571 }, - { url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377 }, - { url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394 }, - { url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586 }, - { url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396 }, - { url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577 }, - { url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809 }, - { url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724 }, - { url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535 }, - { url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904 }, - { url = "https://files.pythonhosted.org/packages/d0/a7/a027970c991ca90f24e968999f7d509332daf6b8c3533d68633930aaebac/coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631", size = 212358 }, - { url = "https://files.pythonhosted.org/packages/f2/48/6aaed3651ae83b231556750280682528fea8ac7f1232834573472d83e459/coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f", size = 212620 }, - { url = "https://files.pythonhosted.org/packages/6c/2a/f4b613f3b44d8b9f144847c89151992b2b6b79cbc506dee89ad0c35f209d/coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd", size = 245788 }, - { url = "https://files.pythonhosted.org/packages/04/d2/de4fdc03af5e4e035ef420ed26a703c6ad3d7a07aff2e959eb84e3b19ca8/coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86", size = 243001 }, - { url = "https://files.pythonhosted.org/packages/f5/e8/eed18aa5583b0423ab7f04e34659e51101135c41cd1dcb33ac1d7013a6d6/coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43", size = 244985 }, - { url = "https://files.pythonhosted.org/packages/17/f8/ae9e5cce8885728c934eaa58ebfa8281d488ef2afa81c3dbc8ee9e6d80db/coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1", size = 245152 }, - { url = "https://files.pythonhosted.org/packages/5a/c8/272c01ae792bb3af9b30fac14d71d63371db227980682836ec388e2c57c0/coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751", size = 243123 }, - { url = "https://files.pythonhosted.org/packages/8c/d0/2819a1e3086143c094ab446e3bdf07138527a7b88cb235c488e78150ba7a/coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67", size = 244506 }, - { url = "https://files.pythonhosted.org/packages/8b/4e/9f6117b89152df7b6112f65c7a4ed1f2f5ec8e60c4be8f351d91e7acc848/coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643", size = 214766 }, - { url = "https://files.pythonhosted.org/packages/27/0f/4b59f7c93b52c2c4ce7387c5a4e135e49891bb3b7408dcc98fe44033bbe0/coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a", size = 215568 }, - { url = "https://files.pythonhosted.org/packages/09/1e/9679826336f8c67b9c39a359352882b24a8a7aee48d4c9cad08d38d7510f/coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d", size = 213939 }, - { url = "https://files.pythonhosted.org/packages/bb/5b/5c6b4e7a407359a2e3b27bf9c8a7b658127975def62077d441b93a30dbe8/coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0", size = 213079 }, - { url = "https://files.pythonhosted.org/packages/a2/22/1e2e07279fd2fd97ae26c01cc2186e2258850e9ec125ae87184225662e89/coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d", size = 213299 }, - { url = "https://files.pythonhosted.org/packages/14/c0/4c5125a4b69d66b8c85986d3321520f628756cf524af810baab0790c7647/coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f", size = 256535 }, - { url = "https://files.pythonhosted.org/packages/81/8b/e36a04889dda9960be4263e95e777e7b46f1bb4fc32202612c130a20c4da/coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029", size = 252756 }, - { url = "https://files.pythonhosted.org/packages/98/82/be04eff8083a09a4622ecd0e1f31a2c563dbea3ed848069e7b0445043a70/coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece", size = 254912 }, - { url = "https://files.pythonhosted.org/packages/0f/25/c26610a2c7f018508a5ab958e5b3202d900422cf7cdca7670b6b8ca4e8df/coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683", size = 256144 }, - { url = "https://files.pythonhosted.org/packages/c5/8b/fb9425c4684066c79e863f1e6e7ecebb49e3a64d9f7f7860ef1688c56f4a/coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f", size = 254257 }, - { url = "https://files.pythonhosted.org/packages/93/df/27b882f54157fc1131e0e215b0da3b8d608d9b8ef79a045280118a8f98fe/coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10", size = 255094 }, - { url = "https://files.pythonhosted.org/packages/41/5f/cad1c3dbed8b3ee9e16fa832afe365b4e3eeab1fb6edb65ebbf745eabc92/coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363", size = 215437 }, - { url = "https://files.pythonhosted.org/packages/99/4d/fad293bf081c0e43331ca745ff63673badc20afea2104b431cdd8c278b4c/coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7", size = 216605 }, - { url = "https://files.pythonhosted.org/packages/1f/56/4ee027d5965fc7fc126d7ec1187529cc30cc7d740846e1ecb5e92d31b224/coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c", size = 214392 }, - { url = "https://files.pythonhosted.org/packages/3e/e5/c723545c3fd3204ebde3b4cc4b927dce709d3b6dc577754bb57f63ca4a4a/coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514", size = 204009 }, - { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000 }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - -[[package]] -name = "cryptography" -version = "45.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712 }, - { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335 }, - { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487 }, - { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922 }, - { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433 }, - { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163 }, - { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687 }, - { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623 }, - { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447 }, - { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830 }, - { url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769 }, - { url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441 }, - { url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836 }, - { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746 }, - { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456 }, - { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495 }, - { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540 }, - { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052 }, - { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024 }, - { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442 }, - { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038 }, - { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964 }, - { url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557 }, - { url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508 }, - { url = "https://files.pythonhosted.org/packages/16/33/b38e9d372afde56906a23839302c19abdac1c505bfb4776c1e4b07c3e145/cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", size = 3580103 }, - { url = "https://files.pythonhosted.org/packages/c4/b9/357f18064ec09d4807800d05a48f92f3b369056a12f995ff79549fbb31f1/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", size = 4143732 }, - { url = "https://files.pythonhosted.org/packages/c4/9c/7f7263b03d5db329093617648b9bd55c953de0b245e64e866e560f9aac07/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", size = 4385424 }, - { url = "https://files.pythonhosted.org/packages/a6/5a/6aa9d8d5073d5acc0e04e95b2860ef2684b2bd2899d8795fc443013e263b/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", size = 4142438 }, - { url = "https://files.pythonhosted.org/packages/42/1c/71c638420f2cdd96d9c2b287fec515faf48679b33a2b583d0f1eda3a3375/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", size = 4384622 }, - { url = "https://files.pythonhosted.org/packages/ef/ab/e3a055c34e97deadbf0d846e189237d3385dca99e1a7e27384c3b2292041/cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", size = 3328911 }, - { url = "https://files.pythonhosted.org/packages/ea/ba/cf442ae99ef363855ed84b39e0fb3c106ac66b7a7703f3c9c9cfe05412cb/cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", size = 3590512 }, - { url = "https://files.pythonhosted.org/packages/28/9a/a7d5bb87d149eb99a5abdc69a41e4e47b8001d767e5f403f78bfaafc7aa7/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", size = 4146899 }, - { url = "https://files.pythonhosted.org/packages/17/11/9361c2c71c42cc5c465cf294c8030e72fb0c87752bacbd7a3675245e3db3/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", size = 4388900 }, - { url = "https://files.pythonhosted.org/packages/c0/76/f95b83359012ee0e670da3e41c164a0c256aeedd81886f878911581d852f/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", size = 4146422 }, - { url = "https://files.pythonhosted.org/packages/09/ad/5429fcc4def93e577a5407988f89cf15305e64920203d4ac14601a9dc876/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", size = 4388475 }, - { url = "https://files.pythonhosted.org/packages/99/49/0ab9774f64555a1b50102757811508f5ace451cf5dc0a2d074a4b9deca6a/cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", size = 3337594 }, -] - -[[package]] -name = "deprecation" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, -] - -[[package]] -name = "distlib" -version = "0.3.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, -] - -[[package]] -name = "docutils" -version = "0.21.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, -] - -[[package]] -name = "filelock" -version = "3.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, -] - -[[package]] -name = "firebase-admin" -version = "6.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachecontrol" }, - { name = "google-api-core", extra = ["grpc"], marker = "platform_python_implementation != 'PyPy'" }, - { name = "google-api-python-client" }, - { name = "google-cloud-firestore", marker = "platform_python_implementation != 'PyPy'" }, - { name = "google-cloud-storage" }, - { name = "httpx", extra = ["http2"] }, - { name = "pyjwt", extra = ["crypto"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/e9/18872c1032905f06d7dd0ae207085a83ca638d40b922accebf5e5bbf735e/firebase_admin-6.9.0.tar.gz", hash = "sha256:06496c3d1380a8f69e3817045b244ce8578d8ad19af3f85c510ac4d8fe0433ca", size = 117059 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/c2/74656081120255c79e613eade6059c43dd648dfad00798a4cec60fcaae34/firebase_admin-6.9.0-py3-none-any.whl", hash = "sha256:75c261c074dcf33a2bcc8366b94ad96255da3fe24079c30220186d489d553ad5", size = 139720 }, -] - -[[package]] -name = "firebase-functions" -source = { editable = "." } -dependencies = [ - { name = "cloudevents" }, - { name = "firebase-admin" }, - { name = "flask" }, - { name = "flask-cors" }, - { name = "functions-framework" }, - { name = "google-cloud-firestore" }, - { name = "google-events" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "pyyaml" }, - { name = "typing-extensions" }, -] - -[package.optional-dependencies] -dev = [ - { name = "build" }, - { name = "google-cloud-tasks" }, - { name = "mypy" }, - { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "ruff" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-napoleon" }, - { name = "toml" }, - { name = "tox" }, -] -docs = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-napoleon" }, -] -test = [ - { name = "google-cloud-tasks" }, - { name = "pytest" }, - { name = "pytest-cov" }, -] - -[package.metadata] -requires-dist = [ - { name = "build", marker = "extra == 'dev'", specifier = ">=1.0.0" }, - { name = "cloudevents", specifier = ">=1.2.0,<2.0.0" }, - { name = "firebase-admin", specifier = ">=6.0.0" }, - { name = "flask", specifier = ">=2.1.2" }, - { name = "flask-cors", specifier = ">=3.0.10" }, - { name = "functions-framework", specifier = ">=3.0.0" }, - { name = "google-cloud-firestore", specifier = ">=2.11.0" }, - { name = "google-cloud-tasks", marker = "extra == 'dev'", specifier = ">=2.13.1" }, - { name = "google-cloud-tasks", marker = "extra == 'test'", specifier = ">=2.13.1" }, - { name = "google-events", specifier = "==0.5.0" }, - { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, - { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0.0" }, - { name = "pyjwt", extras = ["crypto"], specifier = ">=2.5.0" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.1.2" }, - { name = "pytest", marker = "extra == 'test'", specifier = ">=7.1.2" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=3.0.0" }, - { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=3.0.0" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, - { name = "sphinx", marker = "extra == 'dev'", specifier = ">=6.1.3" }, - { name = "sphinx", marker = "extra == 'docs'", specifier = ">=6.1.3" }, - { name = "sphinxcontrib-napoleon", marker = "extra == 'dev'", specifier = ">=0.7" }, - { name = "sphinxcontrib-napoleon", marker = "extra == 'docs'", specifier = ">=0.7" }, - { name = "toml", marker = "extra == 'dev'", specifier = ">=0.10.2" }, - { name = "tox", marker = "extra == 'dev'", specifier = ">=4.0.0" }, - { name = "typing-extensions", specifier = ">=4.4.0" }, -] -provides-extras = ["dev", "test", "docs"] - -[[package]] -name = "flask" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "blinker" }, - { name = "click" }, - { name = "itsdangerous" }, - { name = "jinja2" }, - { name = "markupsafe" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305 }, -] - -[[package]] -name = "flask-cors" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flask" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/37/bcfa6c7d5eec777c4c7cf45ce6b27631cebe5230caf88d85eadd63edd37a/flask_cors-6.0.1.tar.gz", hash = "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db", size = 13463 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/f8/01bf35a3afd734345528f98d0353f2a978a476528ad4d7e78b70c4d149dd/flask_cors-6.0.1-py3-none-any.whl", hash = "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c", size = 13244 }, -] - -[[package]] -name = "functions-framework" -version = "3.8.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "cloudevents" }, - { name = "flask" }, - { name = "gunicorn", marker = "sys_platform != 'win32'" }, - { name = "watchdog" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/93/1f/367a737a58b53f22113e7f443598a3907d67a5ddf1c45f9d35a6ba642bb0/functions_framework-3.8.3.tar.gz", hash = "sha256:95827698469e3979518d52e32def2f11230465877fef32afd49045013b2a469c", size = 43640 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/52/b0b1fe1b964a9afe6b7da41c88434f09c16bf915cd48b9185e839f96fa6c/functions_framework-3.8.3-py3-none-any.whl", hash = "sha256:fd352272c02ee08b4a3445e234213e43fbdd350365d98e2cce13e1575490bffe", size = 36051 }, -] - -[[package]] -name = "google-api-core" -version = "2.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "googleapis-common-protos" }, - { name = "proto-plus" }, - { name = "protobuf" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807 }, -] - -[package.optional-dependencies] -grpc = [ - { name = "grpcio" }, - { name = "grpcio-status" }, -] - -[[package]] -name = "google-api-python-client" -version = "2.174.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, - { name = "google-auth-httplib2" }, - { name = "httplib2" }, - { name = "uritemplate" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1a/fd/860fef0cf3edbad828e2ab4d2ddee5dfe8e595b6da748ac6c77e95bc7bef/google_api_python_client-2.174.0.tar.gz", hash = "sha256:9eb7616a820b38a9c12c5486f9b9055385c7feb18b20cbafc5c5a688b14f3515", size = 13127872 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/2d/4250b81e8f5309b58650660f403584db6f64067acac74475893a8f33348d/google_api_python_client-2.174.0-py3-none-any.whl", hash = "sha256:f695205ceec97bfaa1590a14282559c4109326c473b07352233a3584cdbf4b89", size = 13650466 }, -] - -[[package]] -name = "google-auth" -version = "2.40.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachetools" }, - { name = "pyasn1-modules" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137 }, -] - -[[package]] -name = "google-auth-httplib2" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "httplib2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253 }, -] - -[[package]] -name = "google-cloud-core" -version = "2.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348 }, -] - -[[package]] -name = "google-cloud-firestore" -version = "2.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core", extra = ["grpc"] }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "proto-plus" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/9d/027b9bf61a44422bcdcb00a2acc59152065b1cffa1fc89da62277730973e/google_cloud_firestore-2.21.0.tar.gz", hash = "sha256:0c37faa8506297f827eefc38feb155247a6dcb9a541289631015d125f1b003f8", size = 528159 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/03/94755c64a2fb85cba734ac05a4f80096b8c0acfab0508c9d52c57f571687/google_cloud_firestore-2.21.0-py3-none-any.whl", hash = "sha256:bf33ccc38a27afc60748d1f9bb7c46b078d0d39d288636bdfd967611d7b3f17f", size = 368813 }, -] - -[[package]] -name = "google-cloud-storage" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "google-crc32c" }, - { name = "google-resumable-media" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/84/6afc2ffdf31f6247a6bab6ba070e073fb05e0fda56adf59ce52ac591a033/google_cloud_storage-3.1.1.tar.gz", hash = "sha256:f9c8f965cafd1d38509f8e2b070339e0e9e5bf050774653bf36213d4ea6104c0", size = 7668109 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/4f/b922e919f6e1ea5905f1427fadf1a3f56a85e79e2b0037fec182f6b437dd/google_cloud_storage-3.1.1-py3-none-any.whl", hash = "sha256:ba7e6ae2be5a7a08742f001e23ec6a0c17d78c620f63bf8e0e7c2cbdddb407de", size = 175464 }, -] - -[[package]] -name = "google-cloud-tasks" -version = "2.19.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core", extra = ["grpc"] }, - { name = "google-auth" }, - { name = "grpc-google-iam-v1" }, - { name = "proto-plus" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/73/47e178f576c1e2db9cdb389930ca26f1cf1313ca4d29cc3331ed09e3190d/google_cloud_tasks-2.19.3.tar.gz", hash = "sha256:e9da534f3793e0de37f9a3b34ac3647420865ca726c0ac72148c4f721c4894d6", size = 343833 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/8e/fe11607117508c24b56e04c77d0ea21b49ab469e180ff2b25f533dd9a85b/google_cloud_tasks-2.19.3-py3-none-any.whl", hash = "sha256:29bcf8cfa455db0bf5cb3e812e518de6a4e8ff748265898a3a15cfaf35927c7e", size = 289699 }, -] - -[[package]] -name = "google-crc32c" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/69/b1b05cf415df0d86691d6a8b4b7e60ab3a6fb6efb783ee5cd3ed1382bfd3/google_crc32c-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:b07d48faf8292b4db7c3d64ab86f950c2e94e93a11fd47271c28ba458e4a0d76", size = 30467 }, - { url = "https://files.pythonhosted.org/packages/44/3d/92f8928ecd671bd5b071756596971c79d252d09b835cdca5a44177fa87aa/google_crc32c-1.7.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7cc81b3a2fbd932a4313eb53cc7d9dde424088ca3a0337160f35d91826880c1d", size = 30311 }, - { url = "https://files.pythonhosted.org/packages/33/42/c2d15a73df79d45ed6b430b9e801d0bd8e28ac139a9012d7d58af50a385d/google_crc32c-1.7.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c67ca0a1f5b56162951a9dae987988679a7db682d6f97ce0f6381ebf0fbea4c", size = 37889 }, - { url = "https://files.pythonhosted.org/packages/57/ea/ac59c86a3c694afd117bb669bde32aaf17d0de4305d01d706495f09cbf19/google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc5319db92daa516b653600794d5b9f9439a9a121f3e162f94b0e1891c7933cb", size = 33028 }, - { url = "https://files.pythonhosted.org/packages/60/44/87e77e8476767a4a93f6cf271157c6d948eacec63688c093580af13b04be/google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcdf5a64adb747610140572ed18d011896e3b9ae5195f2514b7ff678c80f1603", size = 38026 }, - { url = "https://files.pythonhosted.org/packages/c8/bf/21ac7bb305cd7c1a6de9c52f71db0868e104a5b573a4977cd9d0ff830f82/google_crc32c-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:754561c6c66e89d55754106739e22fdaa93fafa8da7221b29c8b8e8270c6ec8a", size = 33476 }, - { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468 }, - { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313 }, - { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048 }, - { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669 }, - { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476 }, - { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470 }, - { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315 }, - { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180 }, - { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794 }, - { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477 }, - { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467 }, - { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309 }, - { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133 }, - { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773 }, - { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475 }, - { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243 }, - { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870 }, - { url = "https://files.pythonhosted.org/packages/0b/43/31e57ce04530794917dfe25243860ec141de9fadf4aa9783dffe7dac7c39/google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8e9afc74168b0b2232fb32dd202c93e46b7d5e4bf03e66ba5dc273bb3559589", size = 28242 }, - { url = "https://files.pythonhosted.org/packages/eb/f3/8b84cd4e0ad111e63e30eb89453f8dd308e3ad36f42305cf8c202461cdf0/google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa8136cc14dd27f34a3221c0f16fd42d8a40e4778273e61a3c19aedaa44daf6b", size = 28049 }, - { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241 }, - { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048 }, -] - -[[package]] -name = "google-events" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "proto-plus" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/cd/cdea628192f5960688342386a5fbc2d6053234bd8c3232412c435cbb7101/google-events-0.5.0.tar.gz", hash = "sha256:c86511402ee758685ad6b7f3d93c3febb3f60122da71498d6ed4b44f097af49c", size = 127152 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/0f/c5f48d7864a8126dfc45008a7c3203c5665214a40a958a8678666966f3b7/google_events-0.5.0-py3-none-any.whl", hash = "sha256:cb81884a23dea4cec1716cfe2a1c88251653681436c48b2c9dfcae92dc3e4bc3", size = 246513 }, -] - -[[package]] -name = "google-resumable-media" -version = "2.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-crc32c" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251 }, -] - -[[package]] -name = "googleapis-common-protos" -version = "1.70.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 }, -] - -[package.optional-dependencies] -grpc = [ - { name = "grpcio" }, -] - -[[package]] -name = "grpc-google-iam-v1" -version = "0.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "googleapis-common-protos", extra = ["grpc"] }, - { name = "grpcio" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242 }, -] - -[[package]] -name = "grpcio" -version = "1.73.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/e8/b43b851537da2e2f03fa8be1aef207e5cbfb1a2e014fbb6b40d24c177cd3/grpcio-1.73.1.tar.gz", hash = "sha256:7fce2cd1c0c1116cf3850564ebfc3264fba75d3c74a7414373f1238ea365ef87", size = 12730355 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/51/a5748ab2773d893d099b92653039672f7e26dd35741020972b84d604066f/grpcio-1.73.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:2d70f4ddd0a823436c2624640570ed6097e40935c9194482475fe8e3d9754d55", size = 5365087 }, - { url = "https://files.pythonhosted.org/packages/ae/12/c5ee1a5dfe93dbc2eaa42a219e2bf887250b52e2e2ee5c036c4695f2769c/grpcio-1.73.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:3841a8a5a66830261ab6a3c2a3dc539ed84e4ab019165f77b3eeb9f0ba621f26", size = 10608921 }, - { url = "https://files.pythonhosted.org/packages/c4/6d/b0c6a8120f02b7d15c5accda6bfc43bc92be70ada3af3ba6d8e077c00374/grpcio-1.73.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:628c30f8e77e0258ab788750ec92059fc3d6628590fb4b7cea8c102503623ed7", size = 5803221 }, - { url = "https://files.pythonhosted.org/packages/a6/7a/3c886d9f1c1e416ae81f7f9c7d1995ae72cd64712d29dab74a6bafacb2d2/grpcio-1.73.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67a0468256c9db6d5ecb1fde4bf409d016f42cef649323f0a08a72f352d1358b", size = 6444603 }, - { url = "https://files.pythonhosted.org/packages/42/07/f143a2ff534982c9caa1febcad1c1073cdec732f6ac7545d85555a900a7e/grpcio-1.73.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b84d65bbdebd5926eb5c53b0b9ec3b3f83408a30e4c20c373c5337b4219ec5", size = 6040969 }, - { url = "https://files.pythonhosted.org/packages/fb/0f/523131b7c9196d0718e7b2dac0310eb307b4117bdbfef62382e760f7e8bb/grpcio-1.73.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c54796ca22b8349cc594d18b01099e39f2b7ffb586ad83217655781a350ce4da", size = 6132201 }, - { url = "https://files.pythonhosted.org/packages/ad/18/010a055410eef1d3a7a1e477ec9d93b091ac664ad93e9c5f56d6cc04bdee/grpcio-1.73.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:75fc8e543962ece2f7ecd32ada2d44c0c8570ae73ec92869f9af8b944863116d", size = 6774718 }, - { url = "https://files.pythonhosted.org/packages/16/11/452bfc1ab39d8ee748837ab8ee56beeae0290861052948785c2c445fb44b/grpcio-1.73.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6a6037891cd2b1dd1406b388660522e1565ed340b1fea2955b0234bdd941a862", size = 6304362 }, - { url = "https://files.pythonhosted.org/packages/1e/1c/c75ceee626465721e5cb040cf4b271eff817aa97388948660884cb7adffa/grpcio-1.73.1-cp310-cp310-win32.whl", hash = "sha256:cce7265b9617168c2d08ae570fcc2af4eaf72e84f8c710ca657cc546115263af", size = 3679036 }, - { url = "https://files.pythonhosted.org/packages/62/2e/42cb31b6cbd671a7b3dbd97ef33f59088cf60e3cf2141368282e26fafe79/grpcio-1.73.1-cp310-cp310-win_amd64.whl", hash = "sha256:6a2b372e65fad38842050943f42ce8fee00c6f2e8ea4f7754ba7478d26a356ee", size = 4340208 }, - { url = "https://files.pythonhosted.org/packages/e4/41/921565815e871d84043e73e2c0e748f0318dab6fa9be872cd042778f14a9/grpcio-1.73.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:ba2cea9f7ae4bc21f42015f0ec98f69ae4179848ad744b210e7685112fa507a1", size = 5363853 }, - { url = "https://files.pythonhosted.org/packages/b0/cc/9c51109c71d068e4d474becf5f5d43c9d63038cec1b74112978000fa72f4/grpcio-1.73.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d74c3f4f37b79e746271aa6cdb3a1d7e4432aea38735542b23adcabaaee0c097", size = 10621476 }, - { url = "https://files.pythonhosted.org/packages/8f/d3/33d738a06f6dbd4943f4d377468f8299941a7c8c6ac8a385e4cef4dd3c93/grpcio-1.73.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5b9b1805a7d61c9e90541cbe8dfe0a593dfc8c5c3a43fe623701b6a01b01d710", size = 5807903 }, - { url = "https://files.pythonhosted.org/packages/5d/47/36deacd3c967b74e0265f4c608983e897d8bb3254b920f8eafdf60e4ad7e/grpcio-1.73.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3215f69a0670a8cfa2ab53236d9e8026bfb7ead5d4baabe7d7dc11d30fda967", size = 6448172 }, - { url = "https://files.pythonhosted.org/packages/0e/64/12d6dc446021684ee1428ea56a3f3712048a18beeadbdefa06e6f8814a6e/grpcio-1.73.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc5eccfd9577a5dc7d5612b2ba90cca4ad14c6d949216c68585fdec9848befb1", size = 6044226 }, - { url = "https://files.pythonhosted.org/packages/72/4b/6bae2d88a006000f1152d2c9c10ffd41d0131ca1198e0b661101c2e30ab9/grpcio-1.73.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc7d7fd520614fce2e6455ba89791458020a39716951c7c07694f9dbae28e9c0", size = 6135690 }, - { url = "https://files.pythonhosted.org/packages/38/64/02c83b5076510784d1305025e93e0d78f53bb6a0213c8c84cfe8a00c5c48/grpcio-1.73.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:105492124828911f85127e4825d1c1234b032cb9d238567876b5515d01151379", size = 6775867 }, - { url = "https://files.pythonhosted.org/packages/42/72/a13ff7ba6c68ccffa35dacdc06373a76c0008fd75777cba84d7491956620/grpcio-1.73.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:610e19b04f452ba6f402ac9aa94eb3d21fbc94553368008af634812c4a85a99e", size = 6308380 }, - { url = "https://files.pythonhosted.org/packages/65/ae/d29d948021faa0070ec33245c1ae354e2aefabd97e6a9a7b6dcf0fb8ef6b/grpcio-1.73.1-cp311-cp311-win32.whl", hash = "sha256:d60588ab6ba0ac753761ee0e5b30a29398306401bfbceffe7d68ebb21193f9d4", size = 3679139 }, - { url = "https://files.pythonhosted.org/packages/af/66/e1bbb0c95ea222947f0829b3db7692c59b59bcc531df84442e413fa983d9/grpcio-1.73.1-cp311-cp311-win_amd64.whl", hash = "sha256:6957025a4608bb0a5ff42abd75bfbb2ed99eda29d5992ef31d691ab54b753643", size = 4342558 }, - { url = "https://files.pythonhosted.org/packages/b8/41/456caf570c55d5ac26f4c1f2db1f2ac1467d5bf3bcd660cba3e0a25b195f/grpcio-1.73.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:921b25618b084e75d424a9f8e6403bfeb7abef074bb6c3174701e0f2542debcf", size = 5334621 }, - { url = "https://files.pythonhosted.org/packages/2a/c2/9a15e179e49f235bb5e63b01590658c03747a43c9775e20c4e13ca04f4c4/grpcio-1.73.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:277b426a0ed341e8447fbf6c1d6b68c952adddf585ea4685aa563de0f03df887", size = 10601131 }, - { url = "https://files.pythonhosted.org/packages/0c/1d/1d39e90ef6348a0964caa7c5c4d05f3bae2c51ab429eb7d2e21198ac9b6d/grpcio-1.73.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:96c112333309493c10e118d92f04594f9055774757f5d101b39f8150f8c25582", size = 5759268 }, - { url = "https://files.pythonhosted.org/packages/8a/2b/2dfe9ae43de75616177bc576df4c36d6401e0959833b2e5b2d58d50c1f6b/grpcio-1.73.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f48e862aed925ae987eb7084409a80985de75243389dc9d9c271dd711e589918", size = 6409791 }, - { url = "https://files.pythonhosted.org/packages/6e/66/e8fe779b23b5a26d1b6949e5c70bc0a5fd08f61a6ec5ac7760d589229511/grpcio-1.73.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a6c2cce218e28f5040429835fa34a29319071079e3169f9543c3fbeff166d2", size = 6003728 }, - { url = "https://files.pythonhosted.org/packages/a9/39/57a18fcef567784108c4fc3f5441cb9938ae5a51378505aafe81e8e15ecc/grpcio-1.73.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65b0458a10b100d815a8426b1442bd17001fdb77ea13665b2f7dc9e8587fdc6b", size = 6103364 }, - { url = "https://files.pythonhosted.org/packages/c5/46/28919d2aa038712fc399d02fa83e998abd8c1f46c2680c5689deca06d1b2/grpcio-1.73.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0a9f3ea8dce9eae9d7cb36827200133a72b37a63896e0e61a9d5ec7d61a59ab1", size = 6749194 }, - { url = "https://files.pythonhosted.org/packages/3d/56/3898526f1fad588c5d19a29ea0a3a4996fb4fa7d7c02dc1be0c9fd188b62/grpcio-1.73.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de18769aea47f18e782bf6819a37c1c528914bfd5683b8782b9da356506190c8", size = 6283902 }, - { url = "https://files.pythonhosted.org/packages/dc/64/18b77b89c5870d8ea91818feb0c3ffb5b31b48d1b0ee3e0f0d539730fea3/grpcio-1.73.1-cp312-cp312-win32.whl", hash = "sha256:24e06a5319e33041e322d32c62b1e728f18ab8c9dbc91729a3d9f9e3ed336642", size = 3668687 }, - { url = "https://files.pythonhosted.org/packages/3c/52/302448ca6e52f2a77166b2e2ed75f5d08feca4f2145faf75cb768cccb25b/grpcio-1.73.1-cp312-cp312-win_amd64.whl", hash = "sha256:303c8135d8ab176f8038c14cc10d698ae1db9c480f2b2823f7a987aa2a4c5646", size = 4334887 }, - { url = "https://files.pythonhosted.org/packages/37/bf/4ca20d1acbefabcaba633ab17f4244cbbe8eca877df01517207bd6655914/grpcio-1.73.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b310824ab5092cf74750ebd8a8a8981c1810cb2b363210e70d06ef37ad80d4f9", size = 5335615 }, - { url = "https://files.pythonhosted.org/packages/75/ed/45c345f284abec5d4f6d77cbca9c52c39b554397eb7de7d2fcf440bcd049/grpcio-1.73.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:8f5a6df3fba31a3485096ac85b2e34b9666ffb0590df0cd044f58694e6a1f6b5", size = 10595497 }, - { url = "https://files.pythonhosted.org/packages/a4/75/bff2c2728018f546d812b755455014bc718f8cdcbf5c84f1f6e5494443a8/grpcio-1.73.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:052e28fe9c41357da42250a91926a3e2f74c046575c070b69659467ca5aa976b", size = 5765321 }, - { url = "https://files.pythonhosted.org/packages/70/3b/14e43158d3b81a38251b1d231dfb45a9b492d872102a919fbf7ba4ac20cd/grpcio-1.73.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c0bf15f629b1497436596b1cbddddfa3234273490229ca29561209778ebe182", size = 6415436 }, - { url = "https://files.pythonhosted.org/packages/e5/3f/81d9650ca40b54338336fd360f36773be8cb6c07c036e751d8996eb96598/grpcio-1.73.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854", size = 6007012 }, - { url = "https://files.pythonhosted.org/packages/55/f4/59edf5af68d684d0f4f7ad9462a418ac517201c238551529098c9aa28cb0/grpcio-1.73.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d958c31cc91ab050bd8a91355480b8e0683e21176522bacea225ce51163f2", size = 6105209 }, - { url = "https://files.pythonhosted.org/packages/e4/a8/700d034d5d0786a5ba14bfa9ce974ed4c976936c2748c2bd87aa50f69b36/grpcio-1.73.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f43ffb3bd415c57224c7427bfb9e6c46a0b6e998754bfa0d00f408e1873dcbb5", size = 6753655 }, - { url = "https://files.pythonhosted.org/packages/1f/29/efbd4ac837c23bc48e34bbaf32bd429f0dc9ad7f80721cdb4622144c118c/grpcio-1.73.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:686231cdd03a8a8055f798b2b54b19428cdf18fa1549bee92249b43607c42668", size = 6287288 }, - { url = "https://files.pythonhosted.org/packages/d8/61/c6045d2ce16624bbe18b5d169c1a5ce4d6c3a47bc9d0e5c4fa6a50ed1239/grpcio-1.73.1-cp313-cp313-win32.whl", hash = "sha256:89018866a096e2ce21e05eabed1567479713ebe57b1db7cbb0f1e3b896793ba4", size = 3668151 }, - { url = "https://files.pythonhosted.org/packages/c2/d7/77ac689216daee10de318db5aa1b88d159432dc76a130948a56b3aa671a2/grpcio-1.73.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a68f8c9966b94dff693670a5cf2b54888a48a5011c5d9ce2295a1a1465ee84f", size = 4335747 }, -] - -[[package]] -name = "grpcio-status" -version = "1.73.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/59/9350a13804f2e407d76b3962c548e023639fc1545056e342c6bad0d4fd30/grpcio_status-1.73.1.tar.gz", hash = "sha256:928f49ccf9688db5f20cd9e45c4578a1d01ccca29aeaabf066f2ac76aa886668", size = 13664 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/50/ee32e6073e2c3a4457be168e2bbf84d02ad9d2c18c4a578a641480c293d4/grpcio_status-1.73.1-py3-none-any.whl", hash = "sha256:538595c32a6c819c32b46a621a51e9ae4ffcd7e7e1bce35f728ef3447e9809b6", size = 14422 }, -] - -[[package]] -name = "gunicorn" -version = "23.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, -] - -[[package]] -name = "h2" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hpack" }, - { name = "hyperframe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, -] - -[[package]] -name = "hpack" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, -] - -[[package]] -name = "httplib2" -version = "0.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyparsing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, -] - -[package.optional-dependencies] -http2 = [ - { name = "h2" }, -] - -[[package]] -name = "hyperframe" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, -] - -[[package]] -name = "identify" -version = "2.6.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "imagesize" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, -] - -[[package]] -name = "importlib-metadata" -version = "8.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, -] - -[[package]] -name = "itsdangerous" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, -] - -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, -] - -[[package]] -name = "msgpack" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/52/f30da112c1dc92cf64f57d08a273ac771e7b29dea10b4b30369b2d7e8546/msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", size = 81799 }, - { url = "https://files.pythonhosted.org/packages/e4/35/7bfc0def2f04ab4145f7f108e3563f9b4abae4ab0ed78a61f350518cc4d2/msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", size = 78278 }, - { url = "https://files.pythonhosted.org/packages/e8/c5/df5d6c1c39856bc55f800bf82778fd4c11370667f9b9e9d51b2f5da88f20/msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", size = 402805 }, - { url = "https://files.pythonhosted.org/packages/20/8e/0bb8c977efecfe6ea7116e2ed73a78a8d32a947f94d272586cf02a9757db/msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", size = 408642 }, - { url = "https://files.pythonhosted.org/packages/59/a1/731d52c1aeec52006be6d1f8027c49fdc2cfc3ab7cbe7c28335b2910d7b6/msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", size = 395143 }, - { url = "https://files.pythonhosted.org/packages/2b/92/b42911c52cda2ba67a6418ffa7d08969edf2e760b09015593c8a8a27a97d/msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", size = 395986 }, - { url = "https://files.pythonhosted.org/packages/61/dc/8ae165337e70118d4dab651b8b562dd5066dd1e6dd57b038f32ebc3e2f07/msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75", size = 402682 }, - { url = "https://files.pythonhosted.org/packages/58/27/555851cb98dcbd6ce041df1eacb25ac30646575e9cd125681aa2f4b1b6f1/msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", size = 406368 }, - { url = "https://files.pythonhosted.org/packages/d4/64/39a26add4ce16f24e99eabb9005e44c663db00e3fce17d4ae1ae9d61df99/msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", size = 65004 }, - { url = "https://files.pythonhosted.org/packages/7d/18/73dfa3e9d5d7450d39debde5b0d848139f7de23bd637a4506e36c9800fd6/msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", size = 71548 }, - { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728 }, - { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279 }, - { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859 }, - { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975 }, - { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528 }, - { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338 }, - { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658 }, - { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124 }, - { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016 }, - { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267 }, - { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359 }, - { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172 }, - { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013 }, - { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905 }, - { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336 }, - { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485 }, - { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182 }, - { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883 }, - { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406 }, - { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558 }, - { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677 }, - { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603 }, - { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504 }, - { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749 }, - { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458 }, - { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976 }, - { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607 }, - { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172 }, - { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347 }, - { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341 }, -] - -[[package]] -name = "mypy" -version = "1.16.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644 }, - { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033 }, - { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645 }, - { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986 }, - { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632 }, - { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391 }, - { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557 }, - { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921 }, - { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887 }, - { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658 }, - { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486 }, - { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482 }, - { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 }, - { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 }, - { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 }, - { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 }, - { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 }, - { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 }, - { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 }, - { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 }, - { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 }, - { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 }, - { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 }, - { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 }, - { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, -] - -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, -] - -[[package]] -name = "pockets" -version = "0.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/8e/0601097cfcce2e8c2297db5080e9719f549c2bd4b94420ddc8d3f848bbca/pockets-0.9.1.tar.gz", hash = "sha256:9320f1a3c6f7a9133fe3b571f283bcf3353cd70249025ae8d618e40e9f7e92b3", size = 24993 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/2f/a4583c70fbd8cd04910e2884bcc2bdd670e884061f7b4d70bc13e632a993/pockets-0.9.1-py2.py3-none-any.whl", hash = "sha256:68597934193c08a08eb2bf6a1d85593f627c22f9b065cc727a4f03f669d96d86", size = 26263 }, -] - -[[package]] -name = "pre-commit" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, -] - -[[package]] -name = "proto-plus" -version = "1.26.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163 }, -] - -[[package]] -name = "protobuf" -version = "6.31.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603 }, - { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283 }, - { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604 }, - { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115 }, - { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070 }, - { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724 }, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, -] - -[[package]] -name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, -] - -[[package]] -name = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pyparsing" -version = "3.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, -] - -[[package]] -name = "pyproject-api" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/fd/437901c891f58a7b9096511750247535e891d2d5a5a6eefbc9386a2b41d5/pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", size = 22710 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e6/c293c06695d4a3ab0260ef124a74ebadba5f4c511ce3a4259e976902c00b/pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948", size = 13158 }, -] - -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, -] - -[[package]] -name = "pytest" -version = "8.4.1" -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/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, -] - -[[package]] -name = "pytest-cov" -version = "6.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644 }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, -] - -[[package]] -name = "requests" -version = "2.32.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, -] - -[[package]] -name = "roman-numerals-py" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, -] - -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 }, -] - -[[package]] -name = "ruff" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649 }, - { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201 }, - { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769 }, - { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902 }, - { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002 }, - { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522 }, - { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264 }, - { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882 }, - { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941 }, - { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887 }, - { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742 }, - { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909 }, - { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005 }, - { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579 }, - { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495 }, - { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485 }, - { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209 }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] - -[[package]] -name = "snowballstemmer" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274 }, -] - -[[package]] -name = "sphinx" -version = "8.1.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.11'" }, - { name = "babel", marker = "python_full_version < '3.11'" }, - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version < '3.11'" }, - { name = "imagesize", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version >= '3.11' and python_full_version < '3.13'", -] -dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.11'" }, - { name = "babel", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.11'" }, - { name = "imagesize", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 }, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, -] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, -] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, -] - -[[package]] -name = "sphinxcontrib-napoleon" -version = "0.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pockets" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fa/eb/ad89500f4cee83187596e07f43ad561f293e8e6e96996005c3319653b89f/sphinxcontrib-napoleon-0.7.tar.gz", hash = "sha256:407382beed396e9f2d7f3043fad6afda95719204a1e1a231ac865f40abcbfcf8", size = 21232 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/f2/6b7627dfe7b4e418e295e254bb15c3a6455f11f8c0ad0d43113f678049c3/sphinxcontrib_napoleon-0.7-py2.py3-none-any.whl", hash = "sha256:711e41a3974bdf110a484aec4c1a556799eb0b3f3b897521a018ad7e2db13fef", size = 17151 }, -] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, -] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, -] - -[[package]] -name = "toml" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, -] - -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, -] - -[[package]] -name = "tox" -version = "4.27.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachetools" }, - { name = "chardet" }, - { name = "colorama" }, - { name = "filelock" }, - { name = "packaging" }, - { name = "platformdirs" }, - { name = "pluggy" }, - { name = "pyproject-api" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/b7/19c01717747076f63c54d871ada081cd711a7c9a7572f2225675c3858b94/tox-4.27.0.tar.gz", hash = "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57", size = 198351 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/3a/30889167f41ecaffb957ec4409e1cbc1d5d558a5bbbdfb734a5b9911930f/tox-4.27.0-py3-none-any.whl", hash = "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20", size = 173441 }, -] - -[[package]] -name = "typing-extensions" -version = "4.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 }, -] - -[[package]] -name = "uritemplate" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488 }, -] - -[[package]] -name = "urllib3" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, -] - -[[package]] -name = "virtualenv" -version = "20.31.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 }, -] - -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 }, - { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 }, - { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 }, - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, - { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 }, - { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, -] - -[[package]] -name = "werkzeug" -version = "3.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, -] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, -] From 1c3230e9a68fd4e4708509c545fafecfd3b19239 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:27:01 -0700 Subject: [PATCH 6/9] fix: resolve all ruff linting issues - Auto-fixed 13 issues with --unsafe-fixes: - Import reordering for better organization - Modernized isinstance() calls to use | operator - Converted .format() to f-strings - Simplified path comparisons - Converted generator to dict comprehension - Removed trailing whitespace - Updated type hints from typing module to built-in types - Manual fixes: - Added noqa comments for exception chaining (B904) where existing pattern is intentional - Implemented __hash__ method for Sentinel class to properly support equality comparison --- example/functions/main.py | 3 +- samples/basic_alerts/functions/main.py | 10 +- samples/basic_db/functions/main.py | 3 +- samples/basic_firestore/functions/main.py | 3 +- samples/basic_params/functions/main.py | 3 +- samples/basic_storage/functions/main.py | 5 +- samples/basic_tasks/functions/main.py | 5 +- setup.py | 1 + .../alerts/app_distribution_fn.py | 8 +- src/firebase_functions/alerts/billing_fn.py | 6 +- .../alerts/crashlytics_fn.py | 10 +- .../alerts/performance_fn.py | 18 ++-- src/firebase_functions/alerts_fn.py | 10 +- src/firebase_functions/db_fn.py | 11 ++- src/firebase_functions/eventarc_fn.py | 5 +- src/firebase_functions/firestore_fn.py | 18 ++-- src/firebase_functions/https_fn.py | 17 ++-- src/firebase_functions/identity_fn.py | 15 +-- src/firebase_functions/logger.py | 11 ++- src/firebase_functions/options.py | 28 +++--- src/firebase_functions/params.py | 10 +- src/firebase_functions/private/_alerts_fn.py | 5 +- .../private/_identity_fn.py | 22 +++-- src/firebase_functions/private/manifest.py | 7 +- .../private/path_pattern.py | 4 +- src/firebase_functions/private/serving.py | 16 ++-- .../private/token_verifier.py | 96 +++++++------------ src/firebase_functions/private/util.py | 20 ++-- src/firebase_functions/pubsub_fn.py | 6 +- src/firebase_functions/remote_config_fn.py | 6 +- src/firebase_functions/scheduler_fn.py | 13 ++- src/firebase_functions/storage_fn.py | 1 + src/firebase_functions/tasks_fn.py | 13 +-- src/firebase_functions/test_lab_fn.py | 6 +- tests/test_db.py | 2 + tests/test_eventarc_fn.py | 2 +- tests/test_firestore_fn.py | 10 +- tests/test_https_fn.py | 1 + tests/test_identity_fn.py | 3 +- tests/test_init.py | 1 + tests/test_logger.py | 4 +- tests/test_manifest.py | 2 +- tests/test_options.py | 7 +- tests/test_params.py | 11 ++- tests/test_path_pattern.py | 3 +- tests/test_pubsub_fn.py | 9 +- tests/test_remote_config_fn.py | 7 +- tests/test_scheduler_fn.py | 10 +- tests/test_storage_fn.py | 3 +- tests/test_tasks_fn.py | 6 +- tests/test_test_lab_fn.py | 11 ++- tests/test_util.py | 11 ++- 52 files changed, 273 insertions(+), 245 deletions(-) diff --git a/example/functions/main.py b/example/functions/main.py index 1de863a..cbe77df 100644 --- a/example/functions/main.py +++ b/example/functions/main.py @@ -2,9 +2,10 @@ Example Firebase Functions written in Python """ -from firebase_functions import https_fn, options, params, pubsub_fn from firebase_admin import initialize_app +from firebase_functions import https_fn, options, params, pubsub_fn + initialize_app() options.set_global_options( diff --git a/samples/basic_alerts/functions/main.py b/samples/basic_alerts/functions/main.py index 868cc0b..dd5ab38 100644 --- a/samples/basic_alerts/functions/main.py +++ b/samples/basic_alerts/functions/main.py @@ -1,10 +1,12 @@ """Cloud function samples for Firebase Alerts.""" from firebase_functions import alerts_fn -from firebase_functions.alerts import app_distribution_fn -from firebase_functions.alerts import billing_fn -from firebase_functions.alerts import crashlytics_fn -from firebase_functions.alerts import performance_fn +from firebase_functions.alerts import ( + app_distribution_fn, + billing_fn, + crashlytics_fn, + performance_fn, +) @alerts_fn.on_alert_published(alert_type=alerts_fn.AlertType.BILLING_PLAN_UPDATE) diff --git a/samples/basic_db/functions/main.py b/samples/basic_db/functions/main.py index ff6bfc0..d28d291 100644 --- a/samples/basic_db/functions/main.py +++ b/samples/basic_db/functions/main.py @@ -2,9 +2,10 @@ Example Firebase Functions for RTDB written in Python """ -from firebase_functions import db_fn, options from firebase_admin import initialize_app +from firebase_functions import db_fn, options + initialize_app() options.set_global_options(region=options.SupportedRegion.EUROPE_WEST1) diff --git a/samples/basic_firestore/functions/main.py b/samples/basic_firestore/functions/main.py index b81be25..778bb0d 100644 --- a/samples/basic_firestore/functions/main.py +++ b/samples/basic_firestore/functions/main.py @@ -2,9 +2,10 @@ Example Firebase Functions for Firestore written in Python """ -from firebase_functions import firestore_fn, options from firebase_admin import initialize_app +from firebase_functions import firestore_fn, options + initialize_app() options.set_global_options(region=options.SupportedRegion.EUROPE_WEST1) diff --git a/samples/basic_params/functions/main.py b/samples/basic_params/functions/main.py index 5a48802..89dbf41 100644 --- a/samples/basic_params/functions/main.py +++ b/samples/basic_params/functions/main.py @@ -2,9 +2,10 @@ Example Function params & inputs. """ -from firebase_functions import storage_fn, params from firebase_admin import initialize_app +from firebase_functions import params, storage_fn + initialize_app() bucket = params.StringParam( diff --git a/samples/basic_storage/functions/main.py b/samples/basic_storage/functions/main.py index f8be7b4..b5e6e9c 100644 --- a/samples/basic_storage/functions/main.py +++ b/samples/basic_storage/functions/main.py @@ -2,10 +2,11 @@ Example Firebase Functions for Storage triggers. """ -from firebase_functions import storage_fn -from firebase_functions.storage_fn import StorageObjectData, CloudEvent from firebase_admin import initialize_app +from firebase_functions import storage_fn +from firebase_functions.storage_fn import CloudEvent, StorageObjectData + initialize_app() diff --git a/samples/basic_tasks/functions/main.py b/samples/basic_tasks/functions/main.py index 5063e6c..b314e56 100644 --- a/samples/basic_tasks/functions/main.py +++ b/samples/basic_tasks/functions/main.py @@ -5,8 +5,9 @@ from firebase_admin import initialize_app from google.cloud import tasks_v2 -from firebase_functions import tasks_fn, https_fn -from firebase_functions.options import SupportedRegion, RetryConfig, RateLimits + +from firebase_functions import https_fn, tasks_fn +from firebase_functions.options import RateLimits, RetryConfig, SupportedRegion app = initialize_app() diff --git a/setup.py b/setup.py index bd8bfa6..4cd6283 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ """ from os import path + from setuptools import find_packages, setup install_requires = [ diff --git a/src/firebase_functions/alerts/app_distribution_fn.py b/src/firebase_functions/alerts/app_distribution_fn.py index de73341..d19f36f 100644 --- a/src/firebase_functions/alerts/app_distribution_fn.py +++ b/src/firebase_functions/alerts/app_distribution_fn.py @@ -19,12 +19,12 @@ import dataclasses as _dataclasses import functools as _functools import typing as _typing + import cloudevents.http as _ce -from firebase_functions.alerts import FirebaseAlertData import firebase_functions.private.util as _util - -from firebase_functions.core import T, CloudEvent +from firebase_functions.alerts import FirebaseAlertData +from firebase_functions.core import CloudEvent, T from firebase_functions.options import AppDistributionOptions @@ -65,7 +65,7 @@ class InAppFeedbackPayload: feedback_report: str """ - Resource name. Format: + Resource name. Format: `projects/{project_number}/apps/{app_id}/releases/{release_id}/feedbackReports/{feedback_id}` """ diff --git a/src/firebase_functions/alerts/billing_fn.py b/src/firebase_functions/alerts/billing_fn.py index 86c3600..6411e5f 100644 --- a/src/firebase_functions/alerts/billing_fn.py +++ b/src/firebase_functions/alerts/billing_fn.py @@ -19,12 +19,12 @@ import dataclasses as _dataclasses import functools as _functools import typing as _typing + import cloudevents.http as _ce -from firebase_functions.alerts import FirebaseAlertData import firebase_functions.private.util as _util - -from firebase_functions.core import T, CloudEvent +from firebase_functions.alerts import FirebaseAlertData +from firebase_functions.core import CloudEvent, T from firebase_functions.options import BillingOptions diff --git a/src/firebase_functions/alerts/crashlytics_fn.py b/src/firebase_functions/alerts/crashlytics_fn.py index ee1fbd1..1d3d25f 100644 --- a/src/firebase_functions/alerts/crashlytics_fn.py +++ b/src/firebase_functions/alerts/crashlytics_fn.py @@ -17,14 +17,16 @@ """ import dataclasses as _dataclasses -import typing as _typing -import cloudevents.http as _ce import datetime as _dt import functools as _functools +import typing as _typing + +import cloudevents.http as _ce + +import firebase_functions.private.util as _util from firebase_functions.alerts import FirebaseAlertData -from firebase_functions.core import T, CloudEvent +from firebase_functions.core import CloudEvent, T from firebase_functions.options import CrashlyticsOptions -import firebase_functions.private.util as _util @_dataclasses.dataclass(frozen=True) diff --git a/src/firebase_functions/alerts/performance_fn.py b/src/firebase_functions/alerts/performance_fn.py index 69cbeca..6f6759e 100644 --- a/src/firebase_functions/alerts/performance_fn.py +++ b/src/firebase_functions/alerts/performance_fn.py @@ -19,12 +19,12 @@ import dataclasses as _dataclasses import functools as _functools import typing as _typing + import cloudevents.http as _ce -from firebase_functions.alerts import FirebaseAlertData import firebase_functions.private.util as _util - -from firebase_functions.core import T, CloudEvent +from firebase_functions.alerts import FirebaseAlertData +from firebase_functions.core import CloudEvent, T from firebase_functions.options import PerformanceOptions @@ -37,19 +37,19 @@ class ThresholdAlertPayload: event_name: str """ - Name of the trace or network request this alert is for + Name of the trace or network request this alert is for (e.g. my_custom_trace, firebase.com/api/123). """ event_type: str """ - The resource type this alert is for (i.e. trace, network request, + The resource type this alert is for (i.e. trace, network request, screen rendering, etc.). """ metric_type: str """ - The metric type this alert is for (i.e. success rate, + The metric type this alert is for (i.e. success rate, response time, duration, etc.). """ @@ -85,15 +85,15 @@ class ThresholdAlertPayload: condition_percentile: float | int | None = None """ - The percentile of the alert condition, can be 0 if percentile + The percentile of the alert condition, can be 0 if percentile is not applicable to the alert condition and omitted; range: [1, 100]. """ app_version: str | None = None """ - The app version this alert was triggered for, can be omitted - if the alert is for a network request (because the alert was + The app version this alert was triggered for, can be omitted + if the alert is for a network request (because the alert was checked against data from all versions of app) or a web app (where the app is versionless). """ diff --git a/src/firebase_functions/alerts_fn.py b/src/firebase_functions/alerts_fn.py index 69f8a36..262008c 100644 --- a/src/firebase_functions/alerts_fn.py +++ b/src/firebase_functions/alerts_fn.py @@ -19,17 +19,17 @@ import dataclasses as _dataclasses import functools as _functools import typing as _typing + import cloudevents.http as _ce -from firebase_functions.alerts import FirebaseAlertData import firebase_functions.private.util as _util - -from firebase_functions.core import T, CloudEvent as _CloudEvent, _with_init -from firebase_functions.options import FirebaseAlertOptions +from firebase_functions.alerts import FirebaseAlertData +from firebase_functions.core import CloudEvent as _CloudEvent +from firebase_functions.core import T, _with_init # Explicitly import AlertType to make it available in the public API. # pylint: disable=unused-import -from firebase_functions.options import AlertType +from firebase_functions.options import FirebaseAlertOptions @_dataclasses.dataclass(frozen=True) diff --git a/src/firebase_functions/db_fn.py b/src/firebase_functions/db_fn.py index 47bfabc..db98f81 100644 --- a/src/firebase_functions/db_fn.py +++ b/src/firebase_functions/db_fn.py @@ -17,16 +17,17 @@ # pylint: disable=protected-access import dataclasses as _dataclass +import datetime as _dt import functools as _functools import typing as _typing -import datetime as _dt -import firebase_functions.private.util as _util -import firebase_functions.private.path_pattern as _path_pattern -import firebase_functions.core as _core + import cloudevents.http as _ce -from firebase_functions.options import DatabaseOptions +import firebase_functions.core as _core +import firebase_functions.private.path_pattern as _path_pattern +import firebase_functions.private.util as _util from firebase_functions.core import Change, T +from firebase_functions.options import DatabaseOptions _event_type_written = "google.firebase.database.ref.v1.written" _event_type_created = "google.firebase.database.ref.v1.created" diff --git a/src/firebase_functions/eventarc_fn.py b/src/firebase_functions/eventarc_fn.py index 93a472d..0e23549 100644 --- a/src/firebase_functions/eventarc_fn.py +++ b/src/firebase_functions/eventarc_fn.py @@ -14,9 +14,10 @@ """Cloud functions to handle Eventarc events.""" # pylint: disable=protected-access -import typing as _typing -import functools as _functools import datetime as _dt +import functools as _functools +import typing as _typing + import cloudevents.http as _ce import firebase_functions.options as _options diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 878920f..22fd79f 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -19,20 +19,20 @@ import dataclasses as _dataclass import functools as _functools import typing as _typing -import google.events.cloud.firestore as _firestore -import google.cloud.firestore_v1 as _firestore_v1 -import firebase_functions.private.util as _util -import firebase_functions.private.path_pattern as _path_pattern -import firebase_functions.core as _core -import cloudevents.http as _ce -from firebase_admin import initialize_app, get_app, _apps, _DEFAULT_APP_NAME +import cloudevents.http as _ce +import google.cloud.firestore_v1 as _firestore_v1 +import google.events.cloud.firestore as _firestore +from firebase_admin import _DEFAULT_APP_NAME, _apps, get_app, initialize_app from google.cloud._helpers import _datetime_to_pb_timestamp +from google.cloud.firestore_v1 import DocumentReference, DocumentSnapshot from google.cloud.firestore_v1 import _helpers as _firestore_helpers -from google.cloud.firestore_v1 import DocumentSnapshot, DocumentReference -from firebase_functions.options import FirestoreOptions +import firebase_functions.core as _core +import firebase_functions.private.path_pattern as _path_pattern +import firebase_functions.private.util as _util from firebase_functions.core import Change +from firebase_functions.options import FirestoreOptions _event_type_written = "google.cloud.firestore.document.v1.written" _event_type_created = "google.cloud.firestore.document.v1.created" diff --git a/src/firebase_functions/https_fn.py b/src/firebase_functions/https_fn.py index 3aafb03..7e692de 100644 --- a/src/firebase_functions/https_fn.py +++ b/src/firebase_functions/https_fn.py @@ -17,18 +17,21 @@ # pylint: disable=protected-access import dataclasses as _dataclasses +import enum as _enum import functools as _functools +import json as _json import typing as _typing + import typing_extensions as _typing_extensions -import enum as _enum -import json as _json -import firebase_functions.private.util as _util -import firebase_functions.core as _core +from flask import Request, Response +from flask import jsonify as _jsonify +from flask import make_response as _make_response +from flask_cors import cross_origin as _cross_origin from functions_framework import logging as _logging -from firebase_functions.options import HttpsOptions, _GLOBAL_OPTIONS -from flask import Request, Response, make_response as _make_response, jsonify as _jsonify -from flask_cors import cross_origin as _cross_origin +import firebase_functions.core as _core +import firebase_functions.private.util as _util +from firebase_functions.options import _GLOBAL_OPTIONS, HttpsOptions class FunctionsErrorCode(str, _enum.Enum): diff --git a/src/firebase_functions/identity_fn.py b/src/firebase_functions/identity_fn.py index 1cf78c3..d2afead 100644 --- a/src/firebase_functions/identity_fn.py +++ b/src/firebase_functions/identity_fn.py @@ -14,19 +14,22 @@ """Cloud functions to handle Eventarc events.""" # pylint: disable=protected-access,cyclic-import -import typing as _typing -import functools as _functools -import datetime as _dt import dataclasses as _dataclasses +import datetime as _dt +import functools as _functools +import typing as _typing from enum import Enum -import firebase_functions.options as _options -import firebase_functions.private.util as _util from flask import ( Request as _Request, +) +from flask import ( Response as _Response, ) +import firebase_functions.options as _options +import firebase_functions.private.util as _util + @_dataclasses.dataclass(frozen=True) class AuthUserInfo: @@ -62,7 +65,7 @@ class AuthUserMetadata: creation_time: _dt.datetime """The date the user was created.""" - last_sign_in_time: _typing.Optional[_dt.datetime] + last_sign_in_time: _dt.datetime | None """The date the user last signed in.""" diff --git a/src/firebase_functions/logger.py b/src/firebase_functions/logger.py index aae0f6a..0e22fc5 100644 --- a/src/firebase_functions/logger.py +++ b/src/firebase_functions/logger.py @@ -6,6 +6,7 @@ import json as _json import sys as _sys import typing as _typing + import typing_extensions as _typing_extensions # If encoding is not 'utf-8', change it to 'utf-8'. @@ -58,19 +59,19 @@ def _entry_from_args(severity: LogSeverity, *args, **kwargs) -> LogEntry: ] ) - other: _typing.Dict[str, _typing.Any] = { + other: dict[str, _typing.Any] = { key: value if isinstance(value, str) else _remove_circular(value) for key, value in kwargs.items() } - entry: _typing.Dict[str, _typing.Any] = {"severity": severity, **other} + entry: dict[str, _typing.Any] = {"severity": severity, **other} if message: entry["message"] = message return _typing.cast(LogEntry, entry) -def _remove_circular(obj: _typing.Any, refs: _typing.Set[_typing.Any] | None = None): +def _remove_circular(obj: _typing.Any, refs: set[_typing.Any] | None = None): """ Removes circular references from the given object and replaces them with "[CIRCULAR]". """ @@ -83,7 +84,7 @@ def _remove_circular(obj: _typing.Any, refs: _typing.Set[_typing.Any] | None = N return "[CIRCULAR]" # For non-primitive objects, add the current object's id to the recursion stack - if not isinstance(obj, (str, int, float, bool, type(None))): + if not isinstance(obj, str | int | float | bool | type(None)): refs.add(id(obj)) # Recursively process the object based on its type @@ -98,7 +99,7 @@ def _remove_circular(obj: _typing.Any, refs: _typing.Set[_typing.Any] | None = N result = obj # Remove the object's id from the recursion stack after processing - if not isinstance(obj, (str, int, float, bool, type(None))): + if not isinstance(obj, str | int | float | bool | type(None)): refs.remove(id(obj)) return result diff --git a/src/firebase_functions/options.py b/src/firebase_functions/options.py index 76be849..badf87e 100644 --- a/src/firebase_functions/options.py +++ b/src/firebase_functions/options.py @@ -17,16 +17,16 @@ """ # pylint: disable=protected-access -import enum as _enum import dataclasses as _dataclasses +import enum as _enum import re as _re import typing as _typing from zoneinfo import ZoneInfo as _ZoneInfo import firebase_functions.private.manifest as _manifest -import firebase_functions.private.util as _util import firebase_functions.private.path_pattern as _path_pattern -from firebase_functions.params import SecretParam, Expression +import firebase_functions.private.util as _util +from firebase_functions.params import Expression, SecretParam Timezone = _ZoneInfo """An alias of the zoneinfo.ZoneInfo for convenience.""" @@ -470,7 +470,7 @@ def _endpoint( retryConfig=retry_config, ), } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) def _required_apis(self) -> list[_manifest.ManifestRequiredApi]: return [ @@ -511,7 +511,7 @@ def _endpoint( **_dataclasses.asdict(super()._endpoint(**kwargs)), "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -536,7 +536,7 @@ def _endpoint( event_type = "google.cloud.pubsub.topic.v1.messagePublished" return _manifest.ManifestEndpoint( **_typing.cast( - _typing.Dict, + dict, _dataclasses.asdict( super()._endpoint(**kwargs, event_filters=event_filters, event_type=event_type) ), @@ -640,7 +640,7 @@ def _endpoint( event_type = "google.firebase.firebasealerts.alerts.v1.published" return _manifest.ManifestEndpoint( **_typing.cast( - _typing.Dict, + dict, _dataclasses.asdict( super()._endpoint( **kwargs, @@ -778,7 +778,7 @@ def _endpoint( event_filters = {} if self.filters is None else self.filters endpoint = _manifest.ManifestEndpoint( **_typing.cast( - _typing.Dict, + dict, _dataclasses.asdict( super()._endpoint( **kwargs, @@ -871,7 +871,7 @@ def _endpoint( retryConfig=retry_config, ), } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) def _required_apis(self) -> list[_manifest.ManifestRequiredApi]: return [ @@ -923,7 +923,7 @@ def _endpoint( **_dataclasses.asdict(super()._endpoint(**kwargs)), "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -977,7 +977,7 @@ def _endpoint( **_dataclasses.asdict(super()._endpoint(**kwargs)), "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -1021,7 +1021,7 @@ def _endpoint( **_dataclasses.asdict(super()._endpoint(**kwargs)), "blockingTrigger": blocking_trigger, } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) def _required_apis(self) -> list[_manifest.ManifestRequiredApi]: return [ @@ -1084,7 +1084,7 @@ def _endpoint( **_dataclasses.asdict(super()._endpoint(**kwargs)), "eventTrigger": event_trigger, } - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) @_dataclasses.dataclass(frozen=True, kw_only=True) @@ -1149,7 +1149,7 @@ def _endpoint( https_trigger["invoker"] = invoker kwargs_merged["httpsTrigger"] = https_trigger - return _manifest.ManifestEndpoint(**_typing.cast(_typing.Dict, kwargs_merged)) + return _manifest.ManifestEndpoint(**_typing.cast(dict, kwargs_merged)) _GLOBAL_OPTIONS = RuntimeOptions() diff --git a/src/firebase_functions/params.py b/src/firebase_functions/params.py index 6284c08..32853f0 100644 --- a/src/firebase_functions/params.py +++ b/src/firebase_functions/params.py @@ -14,11 +14,11 @@ """Module for params that can make Cloud Functions codebases generic.""" import abc as _abc -import json as _json import dataclasses as _dataclasses +import enum as _enum +import json as _json import os as _os import re as _re -import enum as _enum import typing as _typing _T = _typing.TypeVar("_T", str, int, float, bool, list) @@ -327,7 +327,7 @@ def value(self) -> str: if self.default is not None: return self.default.value if isinstance(self.default, Expression) else self.default - return str() + return "" @_dataclasses.dataclass(frozen=True) @@ -340,7 +340,7 @@ def value(self) -> int: return int(_os.environ[self.name]) if self.default is not None: return self.default.value if isinstance(self.default, Expression) else self.default - return int() + return 0 @_dataclasses.dataclass(frozen=True) @@ -357,7 +357,7 @@ def value(self) -> float: return float(_os.environ[self.name]) if self.default is not None: return self.default.value if isinstance(self.default, Expression) else self.default - return float() + return 0.0 @_dataclasses.dataclass(frozen=True) diff --git a/src/firebase_functions/private/_alerts_fn.py b/src/firebase_functions/private/_alerts_fn.py index b13a698..b7796ee 100644 --- a/src/firebase_functions/private/_alerts_fn.py +++ b/src/firebase_functions/private/_alerts_fn.py @@ -15,12 +15,13 @@ # pylint: disable=protected-access,cyclic-import import typing as _typing + import cloudevents.http as _ce +from functions_framework import logging as _logging + import firebase_functions.private.util as _util from firebase_functions.alerts import FirebaseAlertData -from functions_framework import logging as _logging - def plan_update_payload_from_ce_payload(payload: dict): from firebase_functions.alerts.billing_fn import PlanUpdatePayload diff --git a/src/firebase_functions/private/_identity_fn.py b/src/firebase_functions/private/_identity_fn.py index f91ee84..0e09c04 100644 --- a/src/firebase_functions/private/_identity_fn.py +++ b/src/firebase_functions/private/_identity_fn.py @@ -14,24 +14,30 @@ """Cloud functions to handle Eventarc events.""" # pylint: disable=protected-access -import typing as _typing import datetime as _dt -import time as _time import json as _json +import time as _time +import typing as _typing -from firebase_functions.core import _with_init -from firebase_functions.https_fn import HttpsError, FunctionsErrorCode - -import firebase_functions.private.util as _util -import firebase_functions.private.token_verifier as _token_verifier from flask import ( Request as _Request, +) +from flask import ( Response as _Response, - make_response as _make_response, +) +from flask import ( jsonify as _jsonify, ) +from flask import ( + make_response as _make_response, +) from functions_framework import logging as _logging +import firebase_functions.private.token_verifier as _token_verifier +import firebase_functions.private.util as _util +from firebase_functions.core import _with_init +from firebase_functions.https_fn import FunctionsErrorCode, HttpsError + _claims_max_payload_size = 1000 _disallowed_custom_claims = [ "acr", diff --git a/src/firebase_functions/private/manifest.py b/src/firebase_functions/private/manifest.py index 74fdb33..7672a9f 100644 --- a/src/firebase_functions/private/manifest.py +++ b/src/firebase_functions/private/manifest.py @@ -19,11 +19,12 @@ import dataclasses as _dataclasses import typing as _typing +from enum import Enum as _Enum + import typing_extensions as _typing_extensions import firebase_functions.params as _params import firebase_functions.private.util as _util -from enum import Enum as _Enum class SecretEnvironmentVariable(_typing.TypedDict): @@ -211,7 +212,7 @@ def _param_input_to_spec( }, } - if isinstance(param_input, (_params.MultiSelectInput, _params.SelectInput)): + if isinstance(param_input, _params.MultiSelectInput | _params.SelectInput): key = "select" if isinstance(param_input, _params.SelectInput) else "multiSelect" return { key: { @@ -280,7 +281,7 @@ def _object_to_spec(data) -> object: return data -def _dict_factory(data: list[_typing.Tuple[str, _typing.Any]]) -> dict: +def _dict_factory(data: list[tuple[str, _typing.Any]]) -> dict: out: dict = {} for key, value in data: if value is not None: diff --git a/src/firebase_functions/private/path_pattern.py b/src/firebase_functions/private/path_pattern.py index 8391772..1603dba 100644 --- a/src/firebase_functions/private/path_pattern.py +++ b/src/firebase_functions/private/path_pattern.py @@ -13,12 +13,12 @@ # limitations under the License. """Path pattern matching utilities.""" -from enum import Enum import re +from enum import Enum def path_parts(path: str) -> list[str]: - if not path or path == "" or path == "/": + if not path or path in {"", "/"}: return [] return path.strip("/").split("/") diff --git a/src/firebase_functions/private/serving.py b/src/firebase_functions/private/serving.py index e943910..7a2f21c 100644 --- a/src/firebase_functions/private/serving.py +++ b/src/firebase_functions/private/serving.py @@ -16,20 +16,20 @@ """ # pylint: disable=protected-access -import os -import inspect import enum -import yaml import importlib +import inspect +import os import sys -from os import kill, getpid +from os import getpid, kill from signal import SIGTERM -from flask import Flask -from flask import Response +import yaml +from flask import Flask, Response +from firebase_functions import options as _options +from firebase_functions import params as _params from firebase_functions.private import manifest as _manifest -from firebase_functions import params as _params, options as _options from firebase_functions.private import util as _util @@ -62,7 +62,7 @@ def convert_value(obj): return list(map(convert_value, obj)) return obj - without_nones = dict((k, convert_value(v)) for k, v in data.items() if v is not None) + without_nones = {k: convert_value(v) for k, v in data.items() if v is not None} return without_nones diff --git a/src/firebase_functions/private/token_verifier.py b/src/firebase_functions/private/token_verifier.py index 9eeb725..9be797f 100644 --- a/src/firebase_functions/private/token_verifier.py +++ b/src/firebase_functions/private/token_verifier.py @@ -15,19 +15,19 @@ Module for internal token verification. """ +import google.auth.exceptions +import google.oauth2.id_token +import google.oauth2.service_account from firebase_admin import ( + _DEFAULT_APP_NAME, + _apps, + _auth_utils, _token_gen, exceptions, - _auth_utils, - initialize_app, get_app, - _apps, - _DEFAULT_APP_NAME, + initialize_app, ) from google.auth import jwt -import google.auth.exceptions -import google.oauth2.id_token -import google.oauth2.service_account # pylint: disable=consider-using-f-string @@ -48,9 +48,9 @@ def __init__(self, **kwargs): self.issuer = kwargs.pop("issuer") self.expected_audience = kwargs.pop("expected_audience") if self.short_name[0].lower() in "aeiou": - self.articled_short_name = "an {0}".format(self.short_name) + self.articled_short_name = f"an {self.short_name}" else: - self.articled_short_name = "a {0}".format(self.short_name) + self.articled_short_name = f"a {self.short_name}" self._invalid_token_error = kwargs.pop("invalid_token_error") self._expired_token_error = kwargs.pop("expired_token_error") @@ -59,17 +59,15 @@ def verify(self, token, request): token = token.encode("utf-8") if isinstance(token, str) else token if not isinstance(token, bytes) or not token: raise ValueError( - "Illegal {0} provided: {1}. {0} must be a non-empty string.".format( - self.short_name, token - ) + f"Illegal {self.short_name} provided: {token}. {self.short_name} must be a non-empty string." ) if not self.project_id: raise ValueError( "Failed to ascertain project ID from the credential or the environment. Project " - "ID is required to call {0}. Initialize the app with a credentials.Certificate " + f"ID is required to call {self.operation}. Initialize the app with a credentials.Certificate " "or set your Firebase project ID as an app option. Alternatively set the " - "GOOGLE_CLOUD_PROJECT environment variable.".format(self.operation) + "GOOGLE_CLOUD_PROJECT environment variable." ) header, payload = self._decode_unverified(token) @@ -79,83 +77,53 @@ def verify(self, token, request): expected_issuer = self.issuer + self.project_id project_id_match_msg = ( - "Make sure the {0} comes from the same Firebase project as the service account used " - "to authenticate this SDK.".format(self.short_name) - ) - verify_id_token_msg = "See {0} for details on how to retrieve {1}.".format( - self.url, self.short_name + f"Make sure the {self.short_name} comes from the same Firebase project as the service account used " + "to authenticate this SDK." ) + verify_id_token_msg = f"See {self.url} for details on how to retrieve {self.short_name}." emulated = _auth_utils.is_emulated() error_message = None if audience == _token_gen.FIREBASE_AUDIENCE: - error_message = "{0} expects {1}, but was given a custom token.".format( - self.operation, self.articled_short_name - ) + error_message = f"{self.operation} expects {self.articled_short_name}, but was given a custom token." elif not emulated and not header.get("kid"): if ( header.get("alg") == "HS256" and payload.get("v") == 0 and "uid" in payload.get("d", {}) ): - error_message = "{0} expects {1}, but was given a legacy custom token.".format( - self.operation, self.articled_short_name - ) + error_message = f"{self.operation} expects {self.articled_short_name}, but was given a legacy custom token." else: - error_message = 'Firebase {0} has no "kid" claim.'.format(self.short_name) + error_message = f'Firebase {self.short_name} has no "kid" claim.' elif not emulated and header.get("alg") != "RS256": error_message = ( - 'Firebase {0} has incorrect algorithm. Expected "RS256" but got "{1}". {2}'.format( + 'Firebase {} has incorrect algorithm. Expected "RS256" but got "{}". {}'.format( self.short_name, header.get("alg"), verify_id_token_msg ) ) elif not emulated and self.expected_audience and self.expected_audience not in audience: error_message = ( - 'Firebase {0} has incorrect "aud" (audience) claim. Expected "{1}" but ' - 'got "{2}". {3} {4}'.format( - self.short_name, - self.expected_audience, - audience, - project_id_match_msg, - verify_id_token_msg, - ) + f'Firebase {self.short_name} has incorrect "aud" (audience) claim. Expected "{self.expected_audience}" but ' + f'got "{audience}". {project_id_match_msg} {verify_id_token_msg}' ) elif not emulated and not self.expected_audience and audience != self.project_id: error_message = ( - 'Firebase {0} has incorrect "aud" (audience) claim. Expected "{1}" but ' - 'got "{2}". {3} {4}'.format( - self.short_name, - self.project_id, - audience, - project_id_match_msg, - verify_id_token_msg, - ) + f'Firebase {self.short_name} has incorrect "aud" (audience) claim. Expected "{self.project_id}" but ' + f'got "{audience}". {project_id_match_msg} {verify_id_token_msg}' ) elif issuer != expected_issuer: error_message = ( - 'Firebase {0} has incorrect "iss" (issuer) claim. Expected "{1}" but ' - 'got "{2}". {3} {4}'.format( - self.short_name, - expected_issuer, - issuer, - project_id_match_msg, - verify_id_token_msg, - ) + f'Firebase {self.short_name} has incorrect "iss" (issuer) claim. Expected "{expected_issuer}" but ' + f'got "{issuer}". {project_id_match_msg} {verify_id_token_msg}' ) elif subject is None or not isinstance(subject, str): - error_message = 'Firebase {0} has no "sub" (subject) claim. {1}'.format( - self.short_name, verify_id_token_msg - ) + error_message = f'Firebase {self.short_name} has no "sub" (subject) claim. {verify_id_token_msg}' elif not subject: - error_message = 'Firebase {0} has an empty string "sub" (subject) claim. {1}'.format( - self.short_name, verify_id_token_msg - ) + error_message = f'Firebase {self.short_name} has an empty string "sub" (subject) claim. {verify_id_token_msg}' elif len(subject) > 128: error_message = ( - 'Firebase {0} has a "sub" (subject) claim longer than 128 characters. {1}'.format( - self.short_name, verify_id_token_msg - ) + f'Firebase {self.short_name} has a "sub" (subject) claim longer than 128 characters. {verify_id_token_msg}' ) if error_message: @@ -176,11 +144,11 @@ def verify(self, token, request): verified_claims["uid"] = verified_claims["sub"] return verified_claims except google.auth.exceptions.TransportError as error: - raise _token_gen.CertificateFetchError(str(error), cause=error) + raise _token_gen.CertificateFetchError(str(error), cause=error) # noqa: B904 except ValueError as error: if "Token expired" in str(error): - raise self._expired_token_error(str(error), cause=error) - raise self._invalid_token_error(str(error), cause=error) + raise self._expired_token_error(str(error), cause=error) # noqa: B904 + raise self._invalid_token_error(str(error), cause=error) # noqa: B904 def _decode_unverified(self, token): try: @@ -188,7 +156,7 @@ def _decode_unverified(self, token): payload = jwt.decode(token, verify=False) return header, payload except ValueError as error: - raise self._invalid_token_error(str(error), cause=error) + raise self._invalid_token_error(str(error), cause=error) # noqa: B904 class InvalidAuthBlockingTokenError(exceptions.InvalidArgumentError): diff --git a/src/firebase_functions/private/util.py b/src/firebase_functions/private/util.py index 8c7dc5c..58a3d25 100644 --- a/src/firebase_functions/private/util.py +++ b/src/firebase_functions/private/util.py @@ -16,17 +16,18 @@ """ import base64 -import os as _os -import json as _json -import re as _re -import typing as _typing import dataclasses as _dataclasses import datetime as _dt import enum as _enum +import json as _json +import os as _os +import re as _re +import typing as _typing + +from firebase_admin import app_check as _app_check +from firebase_admin import auth as _auth from flask import Request as _Request from functions_framework import logging as _logging -from firebase_admin import auth as _auth -from firebase_admin import app_check as _app_check P = _typing.ParamSpec("P") R = _typing.TypeVar("R") @@ -40,6 +41,9 @@ class Sentinel: def __init__(self, description): self.description = description + def __hash__(self): + return hash(self.description) + def __eq__(self, other): return isinstance(other, Sentinel) and self.description == other.description @@ -57,7 +61,7 @@ def set_func_endpoint_attr( func: _typing.Callable[P, _typing.Any], endpoint: _typing.Any, ) -> _typing.Callable[P, _typing.Any]: - setattr(func, "__firebase_endpoint__", endpoint) + func.__firebase_endpoint__ = endpoint return func @@ -331,7 +335,7 @@ def firebase_config() -> None | FirebaseConfig: # explicitly state that the user can set the env to a file: # https://firebase.google.com/docs/admin/setup#initialize-without-parameters try: - with open(config_file, "r", encoding="utf8") as json_file: + with open(config_file, encoding="utf8") as json_file: json_str = json_file.read() except Exception as err: raise ValueError(f"Unable to read file {config_file}. {err}") from err diff --git a/src/firebase_functions/pubsub_fn.py b/src/firebase_functions/pubsub_fn.py index 3b2df27..7297599 100644 --- a/src/firebase_functions/pubsub_fn.py +++ b/src/firebase_functions/pubsub_fn.py @@ -16,16 +16,16 @@ """ # pylint: disable=protected-access +import base64 as _base64 import dataclasses as _dataclasses import datetime as _dt import functools as _functools -import typing as _typing import json as _json -import base64 as _base64 +import typing as _typing + import cloudevents.http as _ce import firebase_functions.private.util as _util - from firebase_functions.core import CloudEvent, T, _with_init from firebase_functions.options import PubSubOptions diff --git a/src/firebase_functions/remote_config_fn.py b/src/firebase_functions/remote_config_fn.py index cdb0d17..bb48aa9 100644 --- a/src/firebase_functions/remote_config_fn.py +++ b/src/firebase_functions/remote_config_fn.py @@ -17,14 +17,14 @@ """ import dataclasses as _dataclasses -import functools as _functools import datetime as _dt +import enum as _enum +import functools as _functools import typing as _typing + import cloudevents.http as _ce -import enum as _enum import firebase_functions.private.util as _util - from firebase_functions.core import CloudEvent, _with_init from firebase_functions.options import EventHandlerOptions diff --git a/src/firebase_functions/scheduler_fn.py b/src/firebase_functions/scheduler_fn.py index 44f4a55..786ec96 100644 --- a/src/firebase_functions/scheduler_fn.py +++ b/src/firebase_functions/scheduler_fn.py @@ -13,25 +13,28 @@ # limitations under the License. """Cloud functions to handle Schedule triggers.""" -import typing as _typing import dataclasses as _dataclasses import datetime as _dt import functools as _functools +import typing as _typing -import firebase_functions.options as _options -import firebase_functions.private.util as _util -from functions_framework import logging as _logging from flask import ( Request as _Request, +) +from flask import ( Response as _Response, +) +from flask import ( make_response as _make_response, ) +from functions_framework import logging as _logging +import firebase_functions.options as _options +import firebase_functions.private.util as _util from firebase_functions.core import _with_init # Export for user convenience. # pylint: disable=unused-import -from firebase_functions.options import Timezone @_dataclasses.dataclass(frozen=True) diff --git a/src/firebase_functions/storage_fn.py b/src/firebase_functions/storage_fn.py index aa8b689..2989567 100644 --- a/src/firebase_functions/storage_fn.py +++ b/src/firebase_functions/storage_fn.py @@ -20,6 +20,7 @@ import datetime as _dt import functools as _functools import typing as _typing + import cloudevents.http as _ce import firebase_functions.private.util as _util diff --git a/src/firebase_functions/tasks_fn.py b/src/firebase_functions/tasks_fn.py index c29763f..1fea65c 100644 --- a/src/firebase_functions/tasks_fn.py +++ b/src/firebase_functions/tasks_fn.py @@ -14,19 +14,20 @@ """Functions to handle Tasks enqueued with Google Cloud Tasks.""" # pylint: disable=protected-access -import typing as _typing -import functools as _functools import dataclasses as _dataclasses +import functools as _functools import json as _json +import typing as _typing -from flask import Request, Response, make_response as _make_response, jsonify as _jsonify +from flask import Request, Response +from flask import jsonify as _jsonify +from flask import make_response as _make_response +from functions_framework import logging as _logging import firebase_functions.core as _core import firebase_functions.options as _options import firebase_functions.private.util as _util -from firebase_functions.https_fn import CallableRequest, HttpsError, FunctionsErrorCode - -from functions_framework import logging as _logging +from firebase_functions.https_fn import CallableRequest, FunctionsErrorCode, HttpsError _C = _typing.Callable[[CallableRequest[_typing.Any]], _typing.Any] _C1 = _typing.Callable[[Request], Response] diff --git a/src/firebase_functions/test_lab_fn.py b/src/firebase_functions/test_lab_fn.py index a3f55bb..9ca8cdb 100644 --- a/src/firebase_functions/test_lab_fn.py +++ b/src/firebase_functions/test_lab_fn.py @@ -17,14 +17,14 @@ """ import dataclasses as _dataclasses -import functools as _functools import datetime as _dt +import enum as _enum +import functools as _functools import typing as _typing + import cloudevents.http as _ce -import enum as _enum import firebase_functions.private.util as _util - from firebase_functions.core import CloudEvent, _with_init from firebase_functions.options import EventHandlerOptions diff --git a/tests/test_db.py b/tests/test_db.py index a0b6967..59af337 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -4,7 +4,9 @@ import unittest from unittest import mock + from cloudevents.http import CloudEvent + from firebase_functions import core, db_fn diff --git a/tests/test_eventarc_fn.py b/tests/test_eventarc_fn.py index 06a9d86..eb0d76a 100644 --- a/tests/test_eventarc_fn.py +++ b/tests/test_eventarc_fn.py @@ -39,7 +39,7 @@ def test_on_custom_event_published_decorator(self): event_type="firebase.extensions.storage-resize-images.v1.complete", )(func) - endpoint = getattr(decorated_func, "__firebase_endpoint__") + endpoint = decorated_func.__firebase_endpoint__ self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.eventTrigger) self.assertEqual( diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py index 9a57a30..0e07b10 100644 --- a/tests/test_firestore_fn.py +++ b/tests/test_firestore_fn.py @@ -21,10 +21,15 @@ class TestFirestore(TestCase): def test_firestore_endpoint_handler_calls_function_with_correct_args(self): with patch.dict("sys.modules", mocked_modules): from cloudevents.http import CloudEvent + + from firebase_functions.firestore_fn import ( + AuthEvent, + ) from firebase_functions.firestore_fn import ( _event_type_created_with_auth_context as event_type, + ) + from firebase_functions.firestore_fn import ( _firestore_endpoint_handler as firestore_endpoint_handler, - AuthEvent, ) from firebase_functions.private import path_pattern @@ -62,9 +67,10 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): def test_calls_init_function(self): with patch.dict("sys.modules", mocked_modules): - from firebase_functions import firestore_fn, core from cloudevents.http import CloudEvent + from firebase_functions import core, firestore_fn + func = Mock(__name__="example_func") hello = None diff --git a/tests/test_https_fn.py b/tests/test_https_fn.py index 8406c3f..1748b36 100644 --- a/tests/test_https_fn.py +++ b/tests/test_https_fn.py @@ -4,6 +4,7 @@ import unittest from unittest.mock import Mock + from flask import Flask, Request from werkzeug.test import EnvironBuilder diff --git a/tests/test_identity_fn.py b/tests/test_identity_fn.py index 4c4dc0a..b3d43fb 100644 --- a/tests/test_identity_fn.py +++ b/tests/test_identity_fn.py @@ -3,7 +3,8 @@ """ import unittest -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import MagicMock, Mock, patch + from flask import Flask, Request from werkzeug.test import EnvironBuilder diff --git a/tests/test_init.py b/tests/test_init.py index 7fcd4a9..4145835 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -3,6 +3,7 @@ """ import unittest + from firebase_functions import core diff --git a/tests/test_logger.py b/tests/test_logger.py index d580ab5..8f6aaee 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -3,8 +3,10 @@ Logger module tests. """ -import pytest import json + +import pytest + from firebase_functions import logger diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 296cf8f..681d90f 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -13,8 +13,8 @@ # limitations under the License. """Manifest unit tests.""" -import firebase_functions.private.manifest as _manifest import firebase_functions.params as _params +import firebase_functions.private.manifest as _manifest full_endpoint = _manifest.ManifestEndpoint( platform="gcfv2", diff --git a/tests/test_options.py b/tests/test_options.py index 20dba5f..2f4ceb8 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -15,10 +15,11 @@ Options unit tests. """ -from firebase_functions import options, https_fn -from firebase_functions import params -from firebase_functions.private.serving import functions_as_yaml, merge_required_apis from pytest import raises + +from firebase_functions import https_fn, options, params +from firebase_functions.private.serving import functions_as_yaml, merge_required_apis + # pylint: disable=protected-access diff --git a/tests/test_params.py b/tests/test_params.py index 11b2552..aabd2b3 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -16,6 +16,7 @@ from os import environ import pytest + from firebase_functions import params @@ -25,7 +26,7 @@ class TestBoolParams: def test_bool_param_value_true_or_false(self): """Testing if bool params correctly returns a true or false value.""" bool_param = params.BoolParam("BOOL_VALUE_TEST1") - for value_true, value_false in zip(["true"], ["false", "anything", "else"]): + for value_true, value_false in zip(["true"], ["false", "anything", "else"], strict=False): environ["BOOL_VALUE_TEST1"] = value_true assert bool_param.value is True, "Failure, params returned False" environ["BOOL_VALUE_TEST1"] = value_false @@ -69,13 +70,13 @@ def test_float_param_value(self): def test_float_param_empty_default(self): """Testing if float params defaults to empty float if no value and no default.""" - assert params._FloatParam("FLOAT_DEFAULT_TEST1").value == float(), ( + assert params._FloatParam("FLOAT_DEFAULT_TEST1").value == 0.0, ( "Failure, params value is not float" ) def test_float_param_default(self): """Testing if float param defaults to provided default value.""" - assert params._FloatParam("FLOAT_DEFAULT_TEST2", default=float(456.789)).value == 456.789, ( + assert params._FloatParam("FLOAT_DEFAULT_TEST2", default=456.789).value == 456.789, ( "Failure, params default value != 456.789" ) @@ -99,7 +100,7 @@ def test_int_param_value(self): def test_int_param_empty_default(self): """Testing if int param defaults to empty int if no value and no default.""" - assert params.IntParam("INT_DEFAULT_TEST1").value == int(), ( + assert params.IntParam("INT_DEFAULT_TEST1").value == 0, ( "Failure, params value is not int" ) @@ -137,7 +138,7 @@ def test_param_name_upper_snake_case(self): def test_string_param_empty_default(self): """Testing if string param defaults to empty string if no value and no default.""" - assert params.StringParam("STRING_DEFAULT_TEST1").value == str(), ( + assert params.StringParam("STRING_DEFAULT_TEST1").value == "", ( "Failure, params value is not a string" ) diff --git a/tests/test_path_pattern.py b/tests/test_path_pattern.py index 20c88e9..780a2a3 100644 --- a/tests/test_path_pattern.py +++ b/tests/test_path_pattern.py @@ -14,7 +14,8 @@ """Path Pattern unit tests.""" from unittest import TestCase -from firebase_functions.private.path_pattern import path_parts, PathPattern, trim_param + +from firebase_functions.private.path_pattern import PathPattern, path_parts, trim_param class TestPathUtilities(TestCase): diff --git a/tests/test_pubsub_fn.py b/tests/test_pubsub_fn.py index 85b7553..d0bf81b 100644 --- a/tests/test_pubsub_fn.py +++ b/tests/test_pubsub_fn.py @@ -13,18 +13,19 @@ # limitations under the License. """PubSub function tests.""" -import unittest import datetime as _dt +import unittest from unittest.mock import MagicMock + from cloudevents.http import CloudEvent as _CloudEvent from firebase_functions import core from firebase_functions.pubsub_fn import ( + CloudEvent, Message, MessagePublishedData, - on_message_published, _message_handler, - CloudEvent, + on_message_published, ) @@ -41,7 +42,7 @@ def test_on_message_published_decorator(self): func = MagicMock() func.__name__ = "testfn" decorated_func = on_message_published(topic="hello-world")(func) - endpoint = getattr(decorated_func, "__firebase_endpoint__") + endpoint = decorated_func.__firebase_endpoint__ self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.eventTrigger) self.assertIsNotNone(endpoint.eventTrigger["eventType"]) diff --git a/tests/test_remote_config_fn.py b/tests/test_remote_config_fn.py index be0341a..4ff7ebb 100644 --- a/tests/test_remote_config_fn.py +++ b/tests/test_remote_config_fn.py @@ -15,16 +15,17 @@ import unittest from unittest.mock import MagicMock + from cloudevents.http import CloudEvent as _CloudEvent from firebase_functions.remote_config_fn import ( CloudEvent, - ConfigUser, ConfigUpdateData, ConfigUpdateOrigin, ConfigUpdateType, - on_config_updated, + ConfigUser, _config_handler, + on_config_updated, ) @@ -41,7 +42,7 @@ def test_on_config_updated_decorator(self): func = MagicMock() func.__name__ = "testfn" decorated_func = on_config_updated()(func) - endpoint = getattr(decorated_func, "__firebase_endpoint__") + endpoint = decorated_func.__firebase_endpoint__ self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.eventTrigger) self.assertIsNotNone(endpoint.eventTrigger["eventType"]) diff --git a/tests/test_scheduler_fn.py b/tests/test_scheduler_fn.py index 8f52fc7..b3a8c82 100644 --- a/tests/test_scheduler_fn.py +++ b/tests/test_scheduler_fn.py @@ -14,11 +14,13 @@ """Scheduler function tests.""" import unittest -from unittest.mock import Mock from datetime import datetime -from flask import Request, Flask +from unittest.mock import Mock + +from flask import Flask, Request from werkzeug.test import EnvironBuilder -from firebase_functions import scheduler_fn, core + +from firebase_functions import core, scheduler_fn class TestScheduler(unittest.TestCase): @@ -38,7 +40,7 @@ def test_on_schedule_decorator(self): decorated_func = scheduler_fn.on_schedule( schedule="* * * * *", timezone=scheduler_fn.Timezone(tz) )(example_func) - endpoint = getattr(decorated_func, "__firebase_endpoint__") + endpoint = decorated_func.__firebase_endpoint__ self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.scheduleTrigger) diff --git a/tests/test_storage_fn.py b/tests/test_storage_fn.py index c42498e..299c9e4 100644 --- a/tests/test_storage_fn.py +++ b/tests/test_storage_fn.py @@ -5,9 +5,10 @@ import unittest from unittest.mock import Mock -from firebase_functions import core, storage_fn from cloudevents.http import CloudEvent +from firebase_functions import core, storage_fn + class TestStorage(unittest.TestCase): """ diff --git a/tests/test_tasks_fn.py b/tests/test_tasks_fn.py index 02ad385..8c76678 100644 --- a/tests/test_tasks_fn.py +++ b/tests/test_tasks_fn.py @@ -14,13 +14,13 @@ """Task Queue function tests.""" import unittest - from unittest.mock import MagicMock, Mock + from flask import Flask, Request from werkzeug.test import EnvironBuilder from firebase_functions import core -from firebase_functions.tasks_fn import on_task_dispatched, CallableRequest +from firebase_functions.tasks_fn import CallableRequest, on_task_dispatched class TestTasks(unittest.TestCase): @@ -37,7 +37,7 @@ def test_on_task_dispatched_decorator(self): func = MagicMock() func.__name__ = "testfn" decorated_func = on_task_dispatched()(func) - endpoint = getattr(decorated_func, "__firebase_endpoint__") + endpoint = decorated_func.__firebase_endpoint__ self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.taskQueueTrigger) diff --git a/tests/test_test_lab_fn.py b/tests/test_test_lab_fn.py index 04b64f9..7d7f642 100644 --- a/tests/test_test_lab_fn.py +++ b/tests/test_test_lab_fn.py @@ -15,18 +15,19 @@ import unittest from unittest.mock import MagicMock, Mock + from cloudevents.http import CloudEvent as _CloudEvent from firebase_functions import core from firebase_functions.test_lab_fn import ( + ClientInfo, CloudEvent, - TestMatrixCompletedData, - TestState, OutcomeSummary, ResultStorage, - ClientInfo, - on_test_matrix_completed, + TestMatrixCompletedData, + TestState, _event_handler, + on_test_matrix_completed, ) @@ -43,7 +44,7 @@ def test_on_test_matrix_completed_decorator(self): func = MagicMock() func.__name__ = "testfn" decorated_func = on_test_matrix_completed()(func) - endpoint = getattr(decorated_func, "__firebase_endpoint__") + endpoint = decorated_func.__firebase_endpoint__ self.assertIsNotNone(endpoint) self.assertIsNotNone(endpoint.eventTrigger) self.assertIsNotNone(endpoint.eventTrigger["eventType"]) diff --git a/tests/test_util.py b/tests/test_util.py index ec7ae1e..34d975d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -15,19 +15,20 @@ Internal utils tests. """ +import datetime as _dt from os import environ, path + from firebase_functions.private.util import ( + PrecisionTimestamp, + _unsafe_decode_id_token, + deep_merge, firebase_config, + get_precision_timestamp, microsecond_timestamp_conversion, nanoseconds_timestamp_conversion, - get_precision_timestamp, normalize_path, - deep_merge, - PrecisionTimestamp, second_timestamp_conversion, - _unsafe_decode_id_token, ) -import datetime as _dt test_bucket = "python-functions-testing.appspot.com" test_config_file = path.join(path.dirname(path.realpath(__file__)), "firebase_config_test.json") From 50ae4284fe2ed088995018bd09cadef7f719a5ea Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:37:02 -0700 Subject: [PATCH 7/9] fix: remove pylint/yapf and fix ruff import issues - Remove pylint from CI workflow and setup.py - Delete .pylintrc configuration file - Remove yapf configuration from pyproject.toml - Update CONTRIBUTING.md to use ruff commands - Update VS Code settings to use ruff formatter and linter - Fix missing imports that were removed during ruff import reorganization: - Restore Timezone export in scheduler_fn.py - Restore AlertType export in alerts_fn.py - Format remaining files with ruff formatter --- .github/CONTRIBUTING.md | 4 +- .github/workflows/ci.yaml | 4 - .pylintrc | 431 ------------------ .vscode/settings.json | 19 +- pyproject.toml | 9 - setup.py | 1 - src/firebase_functions/alerts_fn.py | 2 +- .../private/token_verifier.py | 8 +- src/firebase_functions/scheduler_fn.py | 1 + tests/test_params.py | 4 +- 10 files changed, 18 insertions(+), 465 deletions(-) delete mode 100644 .pylintrc diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6a3c3c4..c70c9d9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -49,7 +49,7 @@ python3.11 -m pytest --cov=src --cov-report term --cov-report html --cov-report ### Formatting code ```bash -yapf -i -r -p . +python3.11 -m ruff format . ``` ### Running lints & type checking @@ -58,7 +58,7 @@ yapf -i -r -p . # Type checking python3.11 -m mypy . # Linting -python3.11 -m pylint $(git ls-files '*.py') +python3.11 -m ruff check . ``` ### Generating Docs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 616d024..f8a5599 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,10 +48,6 @@ jobs: source venv/bin/activate pip3 install --upgrade pip python3.10 -m pip install -e ".[dev]" - - name: Lint with pylint - run: | - source venv/bin/activate - python3.10 -m pylint $(git ls-files '*.py') - name: Lint with ruff run: | source venv/bin/activate diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 0b758b1..0000000 --- a/.pylintrc +++ /dev/null @@ -1,431 +0,0 @@ -# This Pylint rcfile contains a best-effort configuration to uphold the -# best-practices and style described in the Google Python style guide: -# https://google.github.io/styleguide/pyguide.html -# -# Its canonical open-source location is: -# https://google.github.io/styleguide/pylintrc - -[MASTER] - -# Files or directories to be skipped. They should be base names, not paths. -ignore=third_party, setup.py - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=abstract-method, - apply-builtin, - arguments-differ, - attribute-defined-outside-init, - backtick, - bad-option-value, - basestring-builtin, - buffer-builtin, - c-extension-no-member, - consider-using-enumerate, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - div-method, - duplicate-code, - eq-without-hash, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, - fixme, - getslice-method, - global-statement, - hex-method, - idiv-method, - implicit-str-concat-in-sequence, - import-error, - import-self, - import-star-module-level, - inconsistent-return-statements, - input-builtin, - intern-builtin, - invalid-str-codec, - locally-disabled, - long-builtin, - long-suffix, - map-builtin-not-iterating, - misplaced-comparison-constant, - missing-function-docstring, - metaclass-assignment, - next-method-called, - next-method-defined, - no-absolute-import, - no-else-break, - no-else-continue, - no-else-raise, - no-else-return, - no-init, # added - no-member, - no-name-in-module, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - rdiv-method, - reduce-builtin, - relative-import, - reload-builtin, - round-builtin, - setslice-method, - signature-differs, - standarderror-builtin, - suppressed-message, - sys-max-int, - too-few-public-methods, - too-many-ancestors, - too-many-arguments, - too-many-boolean-expressions, - too-many-branches, - too-many-instance-attributes, - too-many-locals, - too-many-nested-blocks, - too-many-public-methods, - too-many-return-statements, - too-many-statements, - trailing-newlines, - unichr-builtin, - unicode-builtin, - unnecessary-pass, - unpacking-in-except, - useless-else-on-loop, - useless-object-inheritance, - useless-suppression, - using-cmp-argument, - wrong-import-order, - xrange-builtin, - zip-builtin-not-iterating, - import-outside-toplevel, - protected-access, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=main,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl - -# Regular expression matching correct function names -function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct constant names -const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct attribute names -attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ - -# Regular expression matching correct argument names -argument-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=^_?[A-Z][a-zA-Z0-9]*$ - -# Regular expression matching correct module names -module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ - -# Regular expression matching correct method names -method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=10 - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt -# lines made too long by directives to pytype. - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=(?x)( - ^\s*(\#\ )??$| - ^\s*(from\s+\S+\s+)?import\s+.+$) - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# Maximum number of lines in a module -max-module-lines=99999 - -# String used as indentation unit. The internal Google style guide mandates 2 -# spaces. Google's externaly-published style guide says 4, consistent with -# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google -# projects (like TensorFlow). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging,absl.logging,tensorflow.io.logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub, - TERMIOS, - Bastion, - rexec, - sets - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant, absl - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls, - class_ - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.StandardError, - builtins.Exception, - builtins.BaseException diff --git a/.vscode/settings.json b/.vscode/settings.json index 22791a0..cb13601 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,14 @@ { - "python.linting.enabled": true, - "python.linting.pylintEnabled": false, - "python.formatting.provider": "yapf", - "python.formatting.yapfArgs": [ - "--style", - "{based_on_style: google, indent_width: 4}" - ], - "python.linting.pylintPath": "pylint", + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "ruff.lint.enable": true, + "ruff.format.enable": true, "python.envFile": "${workspaceFolder}/venv", - "editor.formatOnSave": true, - "python.linting.lintOnSave": true, "python.linting.mypyEnabled": true, "mypy.dmypyExecutable": "${workspaceFolder}/venv/bin/dmypy", "files.autoSave": "afterDelay", diff --git a/pyproject.toml b/pyproject.toml index 29fa6a5..b3ddc86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,15 +12,6 @@ pythonpath = [ [tool.coverage.report] skip_empty = true -[tool.yapf] -based_on_style = "google" -indent_width = 4 -[tool.yapfignore] -ignore_patterns = [ - "venv", - "build", - "dist", -] [tool.ruff] target-version = "py310" diff --git a/setup.py b/setup.py index 4cd6283..901c389 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ dev_requires = [ "pytest>=7.1.2", "setuptools>=63.4.2", - "pylint>=2.16.1", "pytest-cov>=3.0.0", "mypy>=1.0.0", "sphinx>=6.1.3", diff --git a/src/firebase_functions/alerts_fn.py b/src/firebase_functions/alerts_fn.py index 262008c..a710034 100644 --- a/src/firebase_functions/alerts_fn.py +++ b/src/firebase_functions/alerts_fn.py @@ -29,7 +29,7 @@ # Explicitly import AlertType to make it available in the public API. # pylint: disable=unused-import -from firebase_functions.options import FirebaseAlertOptions +from firebase_functions.options import AlertType, FirebaseAlertOptions @_dataclasses.dataclass(frozen=True) diff --git a/src/firebase_functions/private/token_verifier.py b/src/firebase_functions/private/token_verifier.py index 9be797f..096b113 100644 --- a/src/firebase_functions/private/token_verifier.py +++ b/src/firebase_functions/private/token_verifier.py @@ -118,13 +118,13 @@ def verify(self, token, request): f'got "{issuer}". {project_id_match_msg} {verify_id_token_msg}' ) elif subject is None or not isinstance(subject, str): - error_message = f'Firebase {self.short_name} has no "sub" (subject) claim. {verify_id_token_msg}' + error_message = ( + f'Firebase {self.short_name} has no "sub" (subject) claim. {verify_id_token_msg}' + ) elif not subject: error_message = f'Firebase {self.short_name} has an empty string "sub" (subject) claim. {verify_id_token_msg}' elif len(subject) > 128: - error_message = ( - f'Firebase {self.short_name} has a "sub" (subject) claim longer than 128 characters. {verify_id_token_msg}' - ) + error_message = f'Firebase {self.short_name} has a "sub" (subject) claim longer than 128 characters. {verify_id_token_msg}' if error_message: raise self._invalid_token_error(error_message) diff --git a/src/firebase_functions/scheduler_fn.py b/src/firebase_functions/scheduler_fn.py index 786ec96..10f66ea 100644 --- a/src/firebase_functions/scheduler_fn.py +++ b/src/firebase_functions/scheduler_fn.py @@ -35,6 +35,7 @@ # Export for user convenience. # pylint: disable=unused-import +from firebase_functions.options import Timezone @_dataclasses.dataclass(frozen=True) diff --git a/tests/test_params.py b/tests/test_params.py index aabd2b3..02a5ed1 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -100,9 +100,7 @@ def test_int_param_value(self): def test_int_param_empty_default(self): """Testing if int param defaults to empty int if no value and no default.""" - assert params.IntParam("INT_DEFAULT_TEST1").value == 0, ( - "Failure, params value is not int" - ) + assert params.IntParam("INT_DEFAULT_TEST1").value == 0, "Failure, params value is not int" def test_int_param_default(self): """Testing if int param defaults to provided default value.""" From ed218ade3dff25cbb79fe7a438823deeca985a94 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:42:26 -0700 Subject: [PATCH 8/9] fix: add noqa comments and explanations for re-exported imports - Add clear comments explaining why Timezone and AlertType are re-exported - Add noqa: F401 to suppress unused import warnings for intentional re-exports - Fix import ordering to satisfy ruff --- .gitignore | 5 +++++ src/firebase_functions/alerts_fn.py | 9 ++++++--- src/firebase_functions/scheduler_fn.py | 6 +++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6339aeb..8dc880a 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,8 @@ doc/dist .idea .vscode/* !.vscode/settings.json + +# Local development files +async.md +modernization.md +uv.lock diff --git a/src/firebase_functions/alerts_fn.py b/src/firebase_functions/alerts_fn.py index a710034..4deff61 100644 --- a/src/firebase_functions/alerts_fn.py +++ b/src/firebase_functions/alerts_fn.py @@ -27,9 +27,12 @@ from firebase_functions.core import CloudEvent as _CloudEvent from firebase_functions.core import T, _with_init -# Explicitly import AlertType to make it available in the public API. -# pylint: disable=unused-import -from firebase_functions.options import AlertType, FirebaseAlertOptions +# Re-export AlertType from options module so users can import it directly from alerts_fn +# This provides a more convenient API: from firebase_functions.alerts_fn import AlertType +from firebase_functions.options import ( + AlertType, # noqa: F401 + FirebaseAlertOptions, +) @_dataclasses.dataclass(frozen=True) diff --git a/src/firebase_functions/scheduler_fn.py b/src/firebase_functions/scheduler_fn.py index 10f66ea..1979f67 100644 --- a/src/firebase_functions/scheduler_fn.py +++ b/src/firebase_functions/scheduler_fn.py @@ -33,9 +33,9 @@ import firebase_functions.private.util as _util from firebase_functions.core import _with_init -# Export for user convenience. -# pylint: disable=unused-import -from firebase_functions.options import Timezone +# Re-export Timezone from options module so users can import it directly from scheduler_fn +# This provides a more convenient API: from firebase_functions.scheduler_fn import Timezone +from firebase_functions.options import Timezone # noqa: F401 @_dataclasses.dataclass(frozen=True) From 526a578e6cd6711b97e27872ff5430e8f57ed75f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 1 Jul 2025 10:46:13 -0700 Subject: [PATCH 9/9] fix: add type ignore for dynamic attribute assignment - Mypy doesn't understand dynamic attributes on Callable types - Add type: ignore comment to suppress the error - This is a common pattern for decorators that add attributes --- src/firebase_functions/private/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firebase_functions/private/util.py b/src/firebase_functions/private/util.py index 58a3d25..9df0903 100644 --- a/src/firebase_functions/private/util.py +++ b/src/firebase_functions/private/util.py @@ -61,7 +61,7 @@ def set_func_endpoint_attr( func: _typing.Callable[P, _typing.Any], endpoint: _typing.Any, ) -> _typing.Callable[P, _typing.Any]: - func.__firebase_endpoint__ = endpoint + func.__firebase_endpoint__ = endpoint # type: ignore return func