diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..6cb06bf --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,49 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install build dependencies + run: pip install build + + - name: Build package + run: python -m build + + - name: Upload distribution artifacts + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-pypi: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/brickbyte + permissions: + id-token: write + steps: + - name: Download distribution artifacts + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/publish-testpypi.yml b/.github/workflows/publish-testpypi.yml new file mode 100644 index 0000000..1758649 --- /dev/null +++ b/.github/workflows/publish-testpypi.yml @@ -0,0 +1,51 @@ +name: Publish to TestPyPI + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install build dependencies + run: pip install build + + - name: Build package + run: python -m build + + - name: Upload distribution artifacts + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-testpypi: + name: Publish to TestPyPI + needs: build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/brickbyte + permissions: + id-token: write + steps: + - name: Download distribution artifacts + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..db4e0a2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install -e ".[dev]" + + - name: Run tests + run: | + python -m pytest tests/ -v --tb=short + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: pip install ruff + + - name: Run ruff + run: ruff check src/ diff --git a/README.md b/README.md index d51d15b..cb00bb1 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,31 @@ -# BrickByte 🧱 +# Brickbyte 🧱 -**Sync data from Airbyte's 600+ connectors to Databricks in one line.** +**Sync data from 600+ source connectors to Databricks with streaming performance.** -BrickByte wraps [PyAirbyte](https://github.com/airbytehq/airbyte) to make it dead simple to extract data from any source and land it directly into Databricks Unity Catalog. +Brickbyte wraps [PyAirbyte](https://github.com/airbytehq/airbyte) to extract data from any source and streams it directly to Databricks Unity Catalog. + +## Features + +- **600+ Sources** - All Airbyte connectors work out of the box +- **Streaming Architecture** - Bypasses local disk, no OOM issues +- **High Performance** - Uses Unity Catalog Volumes and `COPY INTO` +- **Flexible Output** - Raw JSON or flattened columns +- **AI Enrichment** - Auto-generate table descriptions and detect PII via Foundation Models +- **Preview** - See what schema changes will occur before syncing +- **Simple API** - One-line sync ## Quick Start ```python -%pip install airbyte -%pip install git+https://github.com/park-peter/brickbyte.git --force-reinstall --no-deps +%pip install airbyte databricks-sdk databricks-sql-connector virtualenv +%pip install git+https://github.com/park-peter/brickbyte.git dbutils.library.restartPython() ``` ```python -from brickbyte import BrickByte - -bb = BrickByte() +from brickbyte import Brickbyte +bb = Brickbyte() bb.sync( source="source-faker", source_config={"count": 100}, @@ -25,172 +34,135 @@ bb.sync( ) ``` -That's it. BrickByte handles everything: -- βœ… Installs source connector in isolated venv -- βœ… Installs Databricks destination connector -- βœ… Auto-discovers a running SQL warehouse -- βœ… Auto-authenticates via Databricks SDK -- βœ… Syncs data to Unity Catalog -- βœ… Cleans up after itself +## Output Formats -## Real-World Examples +### Raw Mode (Default) +Stores data as JSON for schema flexibility: + +| id | extracted_at | data | +|----|--------------|------| +| abc-123 | 2026-01-13 10:00:00 | {"displayName": "John", "email": "john@..."} | + +Query with JSON syntax: +```sql +SELECT data:displayName::STRING as name FROM my_table +``` -### GitHub +### Flattened Mode +Expands all fields into columns: ```python -bb.sync( - source="source-github", - source_config={ - "credentials": { - "option_title": "PAT Credentials", - "personal_access_token": "ghp_...", - }, - "repositories": ["owner/repo"], - }, - catalog="main", - schema="raw_github", - streams=["commits", "issues", "pull_requests"], -) +bb.sync(..., flatten=True) ``` -### Confluence +| displayName | email | _id | _extracted_at | +|-------------|-------|-----|---------------| +| John | john@... | abc-123 | 2026-01-13 10:00:00 | + +## Examples + +### Simple Sync (Overwrite) ```python bb.sync( - source="source-confluence", + source="source-github", source_config={ - "domain_name": "your-company.atlassian.net", - "email": "you@company.com", - "api_token": "...", + "credentials": {"personal_access_token": "ghp_..."}, + "repositories": ["owner/repo"], }, catalog="main", - schema="raw_confluence", + schema="bronze", + staging_volume="main.staging.brickbyte_volume", ) ``` -### DataDog +### Flattened Output ```python bb.sync( - source="source-datadog", - source_config={ - "api_key": "...", - "application_key": "...", - "site": "datadoghq.com", - "start_date": "2024-01-01T00:00:00Z", - "end_date": "2024-12-31T23:59:59Z", - }, + source="source-salesforce", + source_config={...}, catalog="main", - schema="raw_datadog", + schema="bronze", + flatten=True, # All fields as top-level columns ) ``` -## API Reference - -### `BrickByte()` +### With AI Metadata Enrichment ```python -bb = BrickByte(base_venv_directory="/tmp/brickbyte") # Optional: custom venv location +result = bb.sync( + source="source-salesforce", + source_config={...}, + catalog="main", + schema="bronze", + enrich_metadata=True, +) +# Tables get: +# - AI-generated table description (COMMENT ON TABLE) +# - Field descriptions stored in TBLPROPERTIES +# - PII detection stored as table TAGS ``` -### `bb.sync()` +### Preview Before Sync ```python -result = bb.sync( - source="source-github", # Required: Airbyte source connector name - source_config={...}, # Required: Source configuration dict - catalog="main", # Required: Unity Catalog name - schema="bronze", # Required: Target schema name - streams=["commits", "issues"], # Optional: List of streams (None = all) - warehouse_id="abc123", # Optional: SQL warehouse ID (auto-discovered) - mode="full_refresh", # Optional: "full_refresh" or "incremental" - cleanup=True, # Optional: Cleanup venvs after sync (default: True) +preview = bb.preview( + source="source-github", + source_config={...}, + catalog="main", + schema="bronze", ) - -print(f"Synced {result.records_written} records") -print(f"Streams: {result.streams_synced}") +print(preview) ``` -## Supported Sources - -BrickByte supports all [600+ Airbyte connectors](https://docs.airbyte.com/integrations): +## Architecture -| Category | Sources | -|----------|---------| -| **CRM** | Salesforce, HubSpot, Pipedrive, Close.com | -| **Marketing** | Facebook Marketing, Google Ads, LinkedIn Ads, TikTok Marketing | -| **Analytics** | Google Analytics, Mixpanel, Amplitude, PostHog, DataDog | -| **Payments** | Stripe, Braintree, PayPal, Chargebee | -| **Support** | Zendesk Support, Intercom, Freshdesk | -| **Databases** | PostgreSQL, MySQL, MongoDB, MSSQL | -| **Files** | S3, GCS, Azure Blob Storage, SFTP | -| **Productivity** | Slack, Notion, Jira, Asana, Airtable, Confluence | -| **E-commerce** | Shopify, Amazon Seller Partner | -| **Dev Tools** | GitHub, GitLab, Sentry | - -## How It Works - -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Databricks Notebook β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ β”‚ -β”‚ bb.sync("source-github", {...}, "main", "bronze") β”‚ -β”‚ β”‚ β”‚ -β”‚ β–Ό β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Airbyte │────▢│ PyAirbyte │────▢│ Databricks Destination β”‚ β”‚ -β”‚ β”‚ Source β”‚ β”‚ Cache β”‚ β”‚ (auto-configured) β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ β”‚ -β”‚ β–Ό β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Unity Catalog Tables β”‚ β”‚ -β”‚ β”‚ _airbyte_raw_ β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` +### Hybrid Mode +Brickbyte automatically selects the best write strategy: -## Data Format +1. **Native Spark** (Default in Databricks Notebooks/Jobs) + - Uses `createDataFrame` + micro-batch writes to Delta + - **Fastest performance**. No Volume required. -Data lands in raw tables with this schema: +2. **SQL Streaming** (Remote / Local) + - Writes to Volume β†’ `COPY INTO` via SQL Warehouse + - Robust remote execution. Requires `staging_volume`. -```sql -CREATE TABLE _airbyte_raw_ ( - _airbyte_ab_id STRING, -- Unique record identifier - _airbyte_emitted_at TIMESTAMP, -- When the record was extracted - _airbyte_data STRING -- JSON payload -) ``` +[In Notebook] ──▢ Spark createDataFrame ──▢ Delta Table (No Volume) -Use Databricks SQL or dbt to transform into your preferred schema. - -## Sync Modes - -- **Full Refresh** (default) β€” Replaces all data in destination -- **Incremental** β€” Only syncs new/updated records using state - -```python -bb.sync(..., mode="incremental") +[Remote] ──▢ SQL Streaming ──▢ Volume ──▢ COPY INTO ──▢ Delta Table ``` ## Requirements - Python 3.10+ - Databricks workspace with Unity Catalog -- Running SQL Warehouse (auto-discovered) - -## Contributing +- SQL Warehouse +- Unity Catalog Volume for staging (Required only for Remote/SQL mode) + +## Dependencies + +```toml +[project] +dependencies = [ + "virtualenv", + "databricks-sdk>=0.74.0", + "databricks-sql-connector>=4.2.2", + "airbyte>=0.34.0", + "pyarrow>=14.0.0", +] + +[project.optional-dependencies] +local-spark = ["delta-spark>=3.0.0", "pyspark>=3.5.0"] +``` -Contributions welcome! Please submit a Pull Request. +For local Spark + Delta development: +```bash +pip install brickbyte[local-spark] +``` ## License -MIT License - see [LICENSE](LICENSE) for details. - ---- - -

- Built with ❀️ for the Databricks community -

+Apache-2.0 License diff --git a/integrations/destination-databricks-py/README.md b/integrations/destination-databricks-py/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/integrations/destination-databricks-py/destination_databricks/__init__.py b/integrations/destination-databricks-py/destination_databricks/__init__.py deleted file mode 100644 index 2104e48..0000000 --- a/integrations/destination-databricks-py/destination_databricks/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .destination import DestinationDatabricks - -__all__ = ["DestinationDatabricks"] diff --git a/integrations/destination-databricks-py/destination_databricks/client.py b/integrations/destination-databricks-py/destination_databricks/client.py deleted file mode 100644 index b07215e..0000000 --- a/integrations/destination-databricks-py/destination_databricks/client.py +++ /dev/null @@ -1,29 +0,0 @@ -import os - -from databricks import sql - - -class DatabricksSqlClient: - def __init__(self, - server_hostname: str, - http_path: str, - token: str, - catalog: str, - schema: str - ): - self.server_hostname = server_hostname - self.http_path = http_path - self.token = token - self.catalog = catalog - self.schema = schema - - def open(self): - connection = sql.connect( - server_hostname=os.getenv("DATABRICKS_SERVER_HOSTNAME", self.server_hostname), - http_path=os.getenv("DATABRICKS_HTTP_PATH", self.http_path), - access_token=os.getenv("DATABRICKS_TOKEN", self.token), - catalog=self.catalog, - schema=self.schema - ) - - return connection.cursor() diff --git a/integrations/destination-databricks-py/destination_databricks/destination.py b/integrations/destination-databricks-py/destination_databricks/destination.py deleted file mode 100644 index e612d9a..0000000 --- a/integrations/destination-databricks-py/destination_databricks/destination.py +++ /dev/null @@ -1,85 +0,0 @@ -import json -import logging -from datetime import datetime -from logging import getLogger -from typing import Any, Iterable, Mapping -from uuid import uuid4 - -from airbyte_cdk.destinations import Destination -from airbyte_cdk.models import AirbyteConnectionStatus, AirbyteMessage, ConfiguredAirbyteCatalog, DestinationSyncMode, Status, Type -from destination_databricks.client import DatabricksSqlClient - -from .writer import create_databricks_writer - -logger = getLogger("airbyte") - - -class DestinationDatabricks(Destination): - def write( - self, config: Mapping[str, Any], configured_catalog: ConfiguredAirbyteCatalog, input_messages: Iterable[AirbyteMessage] - ) -> Iterable[AirbyteMessage]: - - """ - TODO - Reads the input stream of messages, config, and catalog to write data to the destination. - - This method returns an iterable (typically a generator of AirbyteMessages via yield) containing state messages received - in the input message stream. Outputting a state message means that every AirbyteRecordMessage which came before it has been - successfully persisted to the destination. This is used to ensure fault tolerance in the case that a sync fails before fully completing, - then the source is given the last state message output from this method as the starting point of the next sync. - - :param config: dict of JSON configuration matching the configuration declared in spec.json - :param configured_catalog: The Configured Catalog describing the schema of the data being received and how it should be persisted in the - destination - :param input_messages: The stream of input messages received from the source - :return: Iterable of AirbyteStateMessages wrapped in AirbyteMessage structs - """ - streams = {s.stream.name for s in configured_catalog.streams} - client = DatabricksSqlClient(**config) - - writer = create_databricks_writer(client, logger) - - for configured_stream in configured_catalog.streams: - if configured_stream.destination_sync_mode == DestinationSyncMode.overwrite: - writer.delete_table(configured_stream.stream.name) - logger.info(f"Stream {configured_stream.stream.name} is wiped.") - writer.create_raw_table(configured_stream.stream.name) - - for message in input_messages: - if message.type == Type.STATE: - yield message - elif message.type == Type.RECORD: - data = message.record.data - stream = message.record.stream - # Skip unselected streams - if stream not in streams: - logger.debug(f"Stream {stream} was not present in configured streams, skipping") - continue - writer.queue_write_data(stream, str(uuid4()), datetime.now(), json.dumps(data)) - - # Flush any leftover messages - writer.flush() - - def check(self, logger: logging.Logger, config: Mapping[str, Any]) -> AirbyteConnectionStatus: - """ - Tests if the input configuration can be used to successfully connect to the destination with the needed permissions - e.g: if a provided API token or password can be used to connect and write to the destination. - - :param logger: Logging object to display debug/info/error to the logs - (logs will not be accessible via airbyte UI if they are not passed to this logger) - :param config: Json object containing the configuration of this destination, content of this json is as specified in - the properties of the spec.json file - - :return: AirbyteConnectionStatus indicating a Success or Failure - """ - try: - client = DatabricksSqlClient(**config) - cursor = client.open() - cursor.execute(f"DROP TABLE IF EXISTS test") - cursor.execute(f"CREATE TABLE if not exists test (x Int32,y VARCHAR)") - cursor.execute(f"INSERT INTO test (x,y) VALUES (%,%)", [1, "yy", 2, "xx"]) - cursor.execute(f"DROP TABLE IF EXISTS test") - cursor.close() - return AirbyteConnectionStatus(status=Status.SUCCEEDED) - except Exception as e: - return AirbyteConnectionStatus(status=Status.FAILED, message=f"An exception occurred: {repr(e)}") diff --git a/integrations/destination-databricks-py/destination_databricks/run.py b/integrations/destination-databricks-py/destination_databricks/run.py deleted file mode 100644 index 198a549..0000000 --- a/integrations/destination-databricks-py/destination_databricks/run.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys - -from destination_databricks import DestinationDatabricks - - -def run(): - DestinationDatabricks().run(sys.argv[1:]) - - -if __name__ == "__main__": - run() diff --git a/integrations/destination-databricks-py/destination_databricks/spec.json b/integrations/destination-databricks-py/destination_databricks/spec.json deleted file mode 100644 index 706e385..0000000 --- a/integrations/destination-databricks-py/destination_databricks/spec.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "documentationUrl": "https://docs.airbyte.com/integrations/destinations/databricks", - "supported_destination_sync_modes": ["overwrite", "append"], - "supportsIncremental": true, - "connectionSpecification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Destination Databricks", - "type": "object", - "required": ["server_hostname", "http_path", "token", "catalog", "schema"], - "additionalProperties": true, - "properties": { - "server_hostname": { - "title": "Server Hostname", - "description": "Hostname of the database.", - "type": "string", - "order": 0 - }, - "http_path": { - "title": "Server HTTP Path", - "description": "HTTP Path of the server.", - "type": "string", - "order": 1 - }, - "token": { - "title": "PAT Token", - "description": "PAT TOken.", - "type": "string", - "order": 2 - }, - "catalog": { - "title": "Catalog Name", - "description": "Name of the catalog.", - "type": "string", - "order": 3 - }, - "schema": { - "title": "Default Schema", - "description": "The default schema was written to.", - "type": "string", - "examples": ["default"], - "default": "default", - "order": 4 - } - } - } -} diff --git a/integrations/destination-databricks-py/destination_databricks/writer.py b/integrations/destination-databricks-py/destination_databricks/writer.py deleted file mode 100644 index e82c1c1..0000000 --- a/integrations/destination-databricks-py/destination_databricks/writer.py +++ /dev/null @@ -1,142 +0,0 @@ -import json -import logging -from collections import defaultdict -from datetime import datetime - -from destination_databricks.client import DatabricksSqlClient - - -class DatabricksSqlWriter: - """ - Base class for shared writer logic. - """ - - flush_interval = 1000 - - def __init__(self, client: DatabricksSqlClient) -> None: - """ - :param client: Databricks SDK connection class with established connection - to the database. - """ - self.client = client - self._buffer = defaultdict(list) - self._values = 0 - - def delete_table(self, name: str) -> None: - """ - Delete the resulting table. - Primarily used in Overwrite strategy to clean up previous data. - - :param name: table name to delete. - """ - cursor = self.client.open() - cursor.execute(f"DROP TABLE IF EXISTS _airbyte_raw_{name}") - cursor.close() - - def create_raw_table(self, name: str): - """ - Create the resulting _airbyte_raw table. - - :param name: table name to create. - """ - query = f""" - CREATE TABLE IF NOT EXISTS _airbyte_raw_{name} ( - _airbyte_ab_id STRING, - _airbyte_emitted_at TIMESTAMP, - _airbyte_data STRING - ) - """ - cursor = self.client.open() - cursor.execute(query) - cursor.close() - - def queue_write_data(self, stream_name: str, id: str, time: datetime, record: str) -> None: - """ - Queue up data in a buffer in memory before writing to the database. - When flush_interval is reached data is persisted. - - :param stream_name: name of the stream for which the data corresponds. - :param id: unique identifier of this data row. - :param time: time of writing. - :param record: string representation of the json data payload. - """ - self._buffer[stream_name].append((id, time, record)) - self._values += 1 - if self._values == self.flush_interval: - self._flush() - - def _flush(self): - """ - Stub for the intermediate data flush that's triggered during the - buffering operation. - """ - raise NotImplementedError() - - def flush(self): - """ - Stub for the data flush at the end of writing operation. - """ - raise NotImplementedError() - - -class DatabricksSqlWriterImpl(DatabricksSqlWriter): - """ - Data writer using the SQL writing strategy. Data is buffered in memory - and flushed using INSERT INTO SQL statement. - """ - - flush_interval = 1000 - - def __init__(self, client: DatabricksSqlClient) -> None: - """ - :param client: Databricks SDK connection class with established connection - to the databse. - """ - super().__init__(client) - - @staticmethod - def quote_value(value): - if isinstance(value, str): - res = value.replace('\'', '\'\'') - return f"'{res}'" # Escape single quotes in strings - elif isinstance(value, dict): - res = json.dumps(value).replace('\'', '\'\'') - return f"'{res}'" # Convert dict to JSON and escape - elif value is None: - return "NULL" # Handle NULLs - elif isinstance(value, datetime): - return f"'{str(value)}'" - else: - return str(value) # For numbers and other types - - def _flush(self) -> None: - """ - Intermediate data flush that's triggered during the - buffering operation. Writes data stored in memory via SQL commands. - Databricks connector insert into table using stage - """ - cursor = self.client.open() - # id, written_at, data - for table, data in self._buffer.items(): - values = ",".join([ - f"({self.quote_value(x)}, {self.quote_value(y)}, {self.quote_value(z)})" - for (x, y, z) in data - ]) - cursor.execute( - f"INSERT INTO _airbyte_raw_{table} (_airbyte_ab_id,_airbyte_emitted_at,_airbyte_data) VALUES {values}", - ) - self._buffer.clear() - self._values = 0 - cursor.close() - - def flush(self) -> None: - """ - Final data flush after all data has been written to memory. - """ - self._flush() - - -def create_databricks_writer(client: DatabricksSqlClient, logger: logging.Logger) -> DatabricksSqlWriter: - logger.info("Using the SQL writing strategy") - writer = DatabricksSqlWriterImpl(client) - return writer diff --git a/integrations/destination-databricks-py/poetry.lock b/integrations/destination-databricks-py/poetry.lock deleted file mode 100644 index d223a5f..0000000 --- a/integrations/destination-databricks-py/poetry.lock +++ /dev/null @@ -1,2717 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "airbyte-cdk" -version = "7.6.0" -description = "A framework for writing Airbyte Connectors." -optional = false -python-versions = "<3.14,>=3.10" -groups = ["main"] -files = [ - {file = "airbyte_cdk-7.6.0-py3-none-any.whl", hash = "sha256:75a7c3302b35a56b2411298caf5dee31c629f26cd3d9c16201a92da07a50df66"}, - {file = "airbyte_cdk-7.6.0.tar.gz", hash = "sha256:188d82f7ffebb11fc28f51a3cc1e6c61d848948b8b23b31bde7f59bf3ba765b4"}, -] - -[package.dependencies] -airbyte-protocol-models-dataclasses = ">=0.17.1,<0.18.0" -anyascii = ">=0.3.2,<0.4.0" -backoff = "*" -boltons = ">=25.0.0,<26.0.0" -cachetools = "*" -click = ">=8.1.8,<9.0.0" -cryptography = ">=44.0.0,<45.0.0" -dateparser = ">=1.2.2,<2.0.0" -dpath = ">=2.1.6,<3.0.0" -dunamai = ">=1.22.0,<2.0.0" -genson = "1.3.0" -google-cloud-secret-manager = ">=2.17.0,<3.0.0" -isodate = ">=0.6.1,<0.7.0" -Jinja2 = ">=3.1.2,<3.2.0" -jsonref = ">=1,<2" -jsonschema = ">=4.17.3,<5.0" -nltk = "3.9.1" -orjson = ">=3.10.7,<4.0.0" -packaging = "*" -pandas = "2.2.3" -pydantic = ">=2.7,<3.0" -pyjwt = ">=2.8.0,<3.0.0" -pyrate-limiter = ">=3.1.0,<3.2.0" -python-dateutil = ">=2.9.0,<3.0.0" -python-ulid = ">=3.0.0,<4.0.0" -pytz = "2024.2" -PyYAML = ">=6.0.1,<7.0.0" -rapidfuzz = ">=3.10.1,<4.0.0" -referencing = ">=0.36.2" -requests = "*" -requests_cache = "*" -rich = "*" -rich-click = ">=1.8.8,<2.0.0" -serpyco-rs = ">=1.10.2,<2.0.0" -setuptools = ">=80.9.0,<81.0.0" -typing-extensions = "*" -unidecode = ">=1.3.8,<2.0.0" -wcmatch = "10.0" -whenever = ">=0.7.3,<0.9.0" -xmltodict = ">=0.13,<0.15" - -[package.extras] -dev = ["pytest (>=7,<8)"] -file-based = ["avro (>=1.11.2,<1.13.0)", "fastavro (>=1.11.0,<2.0.0)", "markdown", "pdf2image (==1.16.3)", "pdfminer.six (==20221105)", "pyarrow (>=19.0.0,<20.0.0)", "pytesseract (==0.3.10)", "python-calamine (==0.2.3)", "python-snappy (==0.7.3)", "unstructured.pytesseract (>=0.3.12)", "unstructured[docx,pptx] (==0.10.27)"] -manifest-server = ["ddtrace (>=3,<4)", "fastapi (>=0.116.1)", "uvicorn (>=0.35.0)"] -sql = ["sqlalchemy (>=2.0,!=2.0.36,<3.0)"] -vector-db-based = ["cohere (>=4.21,<6.0.0)", "langchain_community (>=0.4,<0.5)", "langchain_core (>=1.0.0,<2.0.0)", "langchain_text_splitters (>=1.0.0,<2.0.0)", "openai[embeddings] (==0.27.9)", "tiktoken (==0.8.0)"] - -[[package]] -name = "airbyte-protocol-models-dataclasses" -version = "0.17.1" -description = "Declares the Airbyte Protocol using Python Dataclasses. Dataclasses in Python have less performance overhead compared to Pydantic models, making them a more efficient choice for scenarios where speed and memory usage are critical" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "airbyte_protocol_models_dataclasses-0.17.1-py3-none-any.whl", hash = "sha256:ef83ac56de6208afe0a21ce05bcfbcfc98b98300a76fb3cdf4db2e7f720f1df0"}, - {file = "airbyte_protocol_models_dataclasses-0.17.1.tar.gz", hash = "sha256:cbccfdf84fabd0b6e325cc57fa0682ae9d386fce8fcb5943faa5df2b7e599919"}, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyascii" -version = "0.3.3" -description = "Unicode to ASCII transliteration" -optional = false -python-versions = ">=3.3" -groups = ["main"] -files = [ - {file = "anyascii-0.3.3-py3-none-any.whl", hash = "sha256:f5ab5e53c8781a36b5a40e1296a0eeda2f48c649ef10c3921c1381b1d00dee7a"}, - {file = "anyascii-0.3.3.tar.gz", hash = "sha256:c94e9dd9d47b3d9494eca305fef9447d00b4bf1a32aff85aa746fa3ec7fb95c3"}, -] - -[[package]] -name = "attributes-doc" -version = "0.4.0" -description = "PEP 224 implementation" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "attributes-doc-0.4.0.tar.gz", hash = "sha256:b1576c94a714e9fc2c65c47cf10d0c8e1a5f7c4f5ae7f69006be108d95cbfbfb"}, - {file = "attributes_doc-0.4.0-py2.py3-none-any.whl", hash = "sha256:4c3007d9e58f3a6cb4b9c614c4d4ce2d92161581f28e594ddd8241cc3a113bdd"}, -] - -[[package]] -name = "attrs" -version = "24.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, -] - -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] - -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "boltons" -version = "25.0.0" -description = "When they're not builtins, they're boltons." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "boltons-25.0.0-py3-none-any.whl", hash = "sha256:dc9fb38bf28985715497d1b54d00b62ea866eca3938938ea9043e254a3a6ca62"}, - {file = "boltons-25.0.0.tar.gz", hash = "sha256:e110fbdc30b7b9868cb604e3f71d4722dd8f4dcb4a5ddd06028ba8f1ab0b5ace"}, -] - -[[package]] -name = "bracex" -version = "2.5.post1" -description = "Bash style brace expander." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, - {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, -] - -[[package]] -name = "cachetools" -version = "5.5.0" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, - {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, -] - -[[package]] -name = "cattrs" -version = "24.1.2" -description = "Composable complex class support for attrs and dataclasses." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"}, - {file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"}, -] - -[package.dependencies] -attrs = ">=23.1.0" -exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""} - -[package.extras] -bson = ["pymongo (>=4.4.0)"] -cbor2 = ["cbor2 (>=5.4.6)"] -msgpack = ["msgpack (>=1.0.5)"] -msgspec = ["msgspec (>=0.18.5) ; implementation_name == \"cpython\""] -orjson = ["orjson (>=3.9.2) ; implementation_name == \"cpython\""] -pyyaml = ["pyyaml (>=6.0)"] -tomlkit = ["tomlkit (>=0.11.8)"] -ujson = ["ujson (>=5.7.0)"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "cffi" -version = "2.0.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, - {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, - {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, - {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, - {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, - {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, - {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, - {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, - {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, - {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, - {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, - {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, - {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, - {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, - {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, - {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, - {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, - {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, - {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, - {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, - {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, - {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, -] - -[package.dependencies] -pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "click" -version = "8.3.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, - {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} - -[[package]] -name = "cryptography" -version = "44.0.3" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main"] -files = [ - {file = "cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d"}, - {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904"}, - {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44"}, - {file = "cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d"}, - {file = "cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d"}, - {file = "cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f"}, - {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5"}, - {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b"}, - {file = "cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028"}, - {file = "cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c"}, - {file = "cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] -sdist = ["build (>=1.0.0)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "databricks-sql-connector" -version = "4.2.2" -description = "Databricks SQL Connector for Python" -optional = false -python-versions = "<4.0.0,>=3.8.0" -groups = ["main"] -files = [ - {file = "databricks_sql_connector-4.2.2-py3-none-any.whl", hash = "sha256:f76164a225f10c49c12b6895d132082919d2de828e19fe3813eb3dfe8f614fdc"}, - {file = "databricks_sql_connector-4.2.2.tar.gz", hash = "sha256:409632a6adb93e849ecd5faaa2e98192e307c984e85cc7c696fb8e953acb7403"}, -] - -[package.dependencies] -lz4 = ">=4.0.2,<5.0.0" -oauthlib = ">=3.1.0,<4.0.0" -openpyxl = ">=3.0.10,<4.0.0" -pandas = [ - {version = ">=1.2.5,<2.3.0", markers = "python_version >= \"3.8\" and python_version < \"3.13\""}, - {version = ">=2.2.3,<2.3.0", markers = "python_version >= \"3.13\""}, -] -pybreaker = ">=1.0.0,<2.0.0" -pyjwt = ">=2.0.0,<3.0.0" -python-dateutil = ">=2.8.0,<3.0.0" -requests = ">=2.18.1,<3.0.0" -thrift = ">=0.16.0,<0.21.0" -urllib3 = ">=1.26" - -[package.extras] -pyarrow = ["pyarrow (>=14.0.1) ; python_version >= \"3.8\" and python_version < \"3.13\"", "pyarrow (>=18.0.0) ; python_version >= \"3.13\""] - -[[package]] -name = "dateparser" -version = "1.2.2" -description = "Date parsing library designed to parse dates from HTML pages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "dateparser-1.2.2-py3-none-any.whl", hash = "sha256:5a5d7211a09013499867547023a2a0c91d5a27d15dd4dbcea676ea9fe66f2482"}, - {file = "dateparser-1.2.2.tar.gz", hash = "sha256:986316f17cb8cdc23ea8ce563027c5ef12fc725b6fb1d137c14ca08777c5ecf7"}, -] - -[package.dependencies] -python-dateutil = ">=2.7.0" -pytz = ">=2024.2" -regex = ">=2024.9.11" -tzlocal = ">=0.2" - -[package.extras] -calendars = ["convertdate (>=2.2.1)", "hijridate"] -fasttext = ["fasttext (>=0.9.1)", "numpy (>=1.19.3,<2)"] -langdetect = ["langdetect (>=1.0.0)"] - -[[package]] -name = "dpath" -version = "2.2.0" -description = "Filesystem-like pathing and searching for dictionaries" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576"}, - {file = "dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e"}, -] - -[[package]] -name = "dunamai" -version = "1.25.0" -description = "Dynamic version generation" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "dunamai-1.25.0-py3-none-any.whl", hash = "sha256:7f9dc687dd3256e613b6cc978d9daabfd2bb5deb8adc541fc135ee423ffa98ab"}, - {file = "dunamai-1.25.0.tar.gz", hash = "sha256:a7f8360ea286d3dbaf0b6a1473f9253280ac93d619836ad4514facb70c0719d1"}, -] - -[package.dependencies] -packaging = ">=20.9" - -[[package]] -name = "et-xmlfile" -version = "2.0.0" -description = "An implementation of lxml.xmlfile for the standard library" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, - {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "genson" -version = "1.3.0" -description = "GenSON is a powerful, user-friendly JSON Schema generator." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, - {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, -] - -[[package]] -name = "google-api-core" -version = "2.28.1" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c"}, - {file = "google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.0" -googleapis-common-protos = ">=1.56.2,<2.0.0" -grpcio = [ - {version = ">=1.33.2,<2.0.0", optional = true, markers = "extra == \"grpc\""}, - {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, -] -grpcio-status = [ - {version = ">=1.33.2,<2.0.0", optional = true, markers = "extra == \"grpc\""}, - {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, -] -proto-plus = [ - {version = ">=1.22.3,<2.0.0"}, - {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" -requests = ">=2.18.0,<3.0.0" - -[package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] - -[[package]] -name = "google-auth" -version = "2.43.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_auth-2.43.0-py2.py3-none-any.whl", hash = "sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16"}, - {file = "google_auth-2.43.0.tar.gz", hash = "sha256:88228eee5fc21b62a1b5fe773ca15e67778cb07dc8363adcb4a8827b52d81483"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<7.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] -enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] -urllib3 = ["packaging", "urllib3"] - -[[package]] -name = "google-cloud-secret-manager" -version = "2.25.0" -description = "Google Cloud Secret Manager API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_secret_manager-2.25.0-py3-none-any.whl", hash = "sha256:eaf1adce3ff5dc0f24335709eba3410dc7e9d20aeea3e8df5b758e27080ebf14"}, - {file = "google_cloud_secret_manager-2.25.0.tar.gz", hash = "sha256:a3792bb1cb307326908297a61536031ac94852c22248f04ae112ff51a853b561"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" -grpc-google-iam-v1 = ">=0.14.0,<1.0.0" -grpcio = ">=1.33.2,<2.0.0" -proto-plus = [ - {version = ">=1.22.3,<2.0.0"}, - {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, -] -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[[package]] -name = "googleapis-common-protos" -version = "1.72.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, - {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, -] - -[package.dependencies] -grpcio = {version = ">=1.44.0,<2.0.0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0)"] - -[[package]] -name = "grpc-google-iam-v1" -version = "0.14.3" -description = "IAM API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6"}, - {file = "grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389"}, -] - -[package.dependencies] -googleapis-common-protos = {version = ">=1.56.0,<2.0.0", extras = ["grpc"]} -grpcio = ">=1.44.0,<2.0.0" -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[[package]] -name = "grpcio" -version = "1.76.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc"}, - {file = "grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde"}, - {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3"}, - {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990"}, - {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af"}, - {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2"}, - {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6"}, - {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3"}, - {file = "grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b"}, - {file = "grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b"}, - {file = "grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a"}, - {file = "grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c"}, - {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465"}, - {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48"}, - {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da"}, - {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397"}, - {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749"}, - {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00"}, - {file = "grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054"}, - {file = "grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d"}, - {file = "grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8"}, - {file = "grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280"}, - {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4"}, - {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11"}, - {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6"}, - {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8"}, - {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980"}, - {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882"}, - {file = "grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958"}, - {file = "grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347"}, - {file = "grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2"}, - {file = "grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468"}, - {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3"}, - {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb"}, - {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae"}, - {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77"}, - {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03"}, - {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42"}, - {file = "grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f"}, - {file = "grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8"}, - {file = "grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62"}, - {file = "grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd"}, - {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc"}, - {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a"}, - {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba"}, - {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09"}, - {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc"}, - {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc"}, - {file = "grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e"}, - {file = "grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e"}, - {file = "grpcio-1.76.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783"}, - {file = "grpcio-1.76.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d"}, - {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd"}, - {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378"}, - {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70"}, - {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416"}, - {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c"}, - {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886"}, - {file = "grpcio-1.76.0-cp39-cp39-win32.whl", hash = "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f"}, - {file = "grpcio-1.76.0-cp39-cp39-win_amd64.whl", hash = "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a"}, - {file = "grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73"}, -] - -[package.dependencies] -typing-extensions = ">=4.12,<5.0" - -[package.extras] -protobuf = ["grpcio-tools (>=1.76.0)"] - -[[package]] -name = "grpcio-status" -version = "1.76.0" -description = "Status proto mapping for gRPC" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18"}, - {file = "grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.76.0" -protobuf = ">=6.31.1,<7.0.0" - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isodate" -version = "0.6.1" -description = "An ISO 8601 date/time/duration parser and formatter" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, - {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "joblib" -version = "1.5.2" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, - {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, -] - -[[package]] -name = "jsonref" -version = "1.1.0" -description = "jsonref is a library for automatic dereferencing of JSON Reference objects for Python." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"}, - {file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"}, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, - {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" -referencing = ">=0.28.4" -rpds-py = ">=0.7.1" - -[package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, - {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, -] - -[package.dependencies] -referencing = ">=0.31.0" - -[[package]] -name = "lz4" -version = "4.3.3" -description = "LZ4 Bindings for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "lz4-4.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b891880c187e96339474af2a3b2bfb11a8e4732ff5034be919aa9029484cd201"}, - {file = "lz4-4.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:222a7e35137d7539c9c33bb53fcbb26510c5748779364014235afc62b0ec797f"}, - {file = "lz4-4.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76176492ff082657ada0d0f10c794b6da5800249ef1692b35cf49b1e93e8ef7"}, - {file = "lz4-4.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d18718f9d78182c6b60f568c9a9cec8a7204d7cb6fad4e511a2ef279e4cb05"}, - {file = "lz4-4.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cdc60e21ec70266947a48839b437d46025076eb4b12c76bd47f8e5eb8a75dcc"}, - {file = "lz4-4.3.3-cp310-cp310-win32.whl", hash = "sha256:c81703b12475da73a5d66618856d04b1307e43428a7e59d98cfe5a5d608a74c6"}, - {file = "lz4-4.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:43cf03059c0f941b772c8aeb42a0813d68d7081c009542301637e5782f8a33e2"}, - {file = "lz4-4.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30e8c20b8857adef7be045c65f47ab1e2c4fabba86a9fa9a997d7674a31ea6b6"}, - {file = "lz4-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7b1839f795315e480fb87d9bc60b186a98e3e5d17203c6e757611ef7dcef61"}, - {file = "lz4-4.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edfd858985c23523f4e5a7526ca6ee65ff930207a7ec8a8f57a01eae506aaee7"}, - {file = "lz4-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e9c410b11a31dbdc94c05ac3c480cb4b222460faf9231f12538d0074e56c563"}, - {file = "lz4-4.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2507ee9c99dbddd191c86f0e0c8b724c76d26b0602db9ea23232304382e1f21"}, - {file = "lz4-4.3.3-cp311-cp311-win32.whl", hash = "sha256:f180904f33bdd1e92967923a43c22899e303906d19b2cf8bb547db6653ea6e7d"}, - {file = "lz4-4.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:b14d948e6dce389f9a7afc666d60dd1e35fa2138a8ec5306d30cd2e30d36b40c"}, - {file = "lz4-4.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e36cd7b9d4d920d3bfc2369840da506fa68258f7bb176b8743189793c055e43d"}, - {file = "lz4-4.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31ea4be9d0059c00b2572d700bf2c1bc82f241f2c3282034a759c9a4d6ca4dc2"}, - {file = "lz4-4.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33c9a6fd20767ccaf70649982f8f3eeb0884035c150c0b818ea660152cf3c809"}, - {file = "lz4-4.3.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca8fccc15e3add173da91be8f34121578dc777711ffd98d399be35487c934bf"}, - {file = "lz4-4.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d84b479ddf39fe3ea05387f10b779155fc0990125f4fb35d636114e1c63a2e"}, - {file = "lz4-4.3.3-cp312-cp312-win32.whl", hash = "sha256:337cb94488a1b060ef1685187d6ad4ba8bc61d26d631d7ba909ee984ea736be1"}, - {file = "lz4-4.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:5d35533bf2cee56f38ced91f766cd0038b6abf46f438a80d50c52750088be93f"}, - {file = "lz4-4.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:363ab65bf31338eb364062a15f302fc0fab0a49426051429866d71c793c23394"}, - {file = "lz4-4.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a136e44a16fc98b1abc404fbabf7f1fada2bdab6a7e970974fb81cf55b636d0"}, - {file = "lz4-4.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abc197e4aca8b63f5ae200af03eb95fb4b5055a8f990079b5bdf042f568469dd"}, - {file = "lz4-4.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56f4fe9c6327adb97406f27a66420b22ce02d71a5c365c48d6b656b4aaeb7775"}, - {file = "lz4-4.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0e822cd7644995d9ba248cb4b67859701748a93e2ab7fc9bc18c599a52e4604"}, - {file = "lz4-4.3.3-cp38-cp38-win32.whl", hash = "sha256:24b3206de56b7a537eda3a8123c644a2b7bf111f0af53bc14bed90ce5562d1aa"}, - {file = "lz4-4.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:b47839b53956e2737229d70714f1d75f33e8ac26e52c267f0197b3189ca6de24"}, - {file = "lz4-4.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6756212507405f270b66b3ff7f564618de0606395c0fe10a7ae2ffcbbe0b1fba"}, - {file = "lz4-4.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee9ff50557a942d187ec85462bb0960207e7ec5b19b3b48949263993771c6205"}, - {file = "lz4-4.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b901c7784caac9a1ded4555258207d9e9697e746cc8532129f150ffe1f6ba0d"}, - {file = "lz4-4.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d9ec061b9eca86e4dcc003d93334b95d53909afd5a32c6e4f222157b50c071"}, - {file = "lz4-4.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4c7bf687303ca47d69f9f0133274958fd672efaa33fb5bcde467862d6c621f0"}, - {file = "lz4-4.3.3-cp39-cp39-win32.whl", hash = "sha256:054b4631a355606e99a42396f5db4d22046a3397ffc3269a348ec41eaebd69d2"}, - {file = "lz4-4.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:eac9af361e0d98335a02ff12fb56caeb7ea1196cf1a49dbf6f17828a131da807"}, - {file = "lz4-4.3.3.tar.gz", hash = "sha256:01fe674ef2889dbb9899d8a67361e0c4a2c833af5aeb37dd505727cf5d2a131e"}, -] - -[package.extras] -docs = ["sphinx (>=1.6.0)", "sphinx-bootstrap-theme"] -flake8 = ["flake8"] -tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins (>=0.5.0)"] -profiling = ["gprof2dot"] -rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "nltk" -version = "3.9.1" -description = "Natural Language Toolkit" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, - {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, -] - -[package.dependencies] -click = "*" -joblib = "*" -regex = ">=2021.8.3" -tqdm = "*" - -[package.extras] -all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] -corenlp = ["requests"] -machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] -plot = ["matplotlib"] -tgrep = ["pyparsing"] -twitter = ["twython"] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, -] - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[package]] -name = "openpyxl" -version = "3.1.5" -description = "A Python library to read/write Excel 2010 xlsx/xlsm files" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, - {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, -] - -[package.dependencies] -et-xmlfile = "*" - -[[package]] -name = "orjson" -version = "3.11.5" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1"}, - {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc70da619744467d8f1f49a8cadae5ec7bbe054e5232d95f92ed8737f8c5870"}, - {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:073aab025294c2f6fc0807201c76fdaed86f8fc4be52c440fb78fbb759a1ac09"}, - {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:835f26fa24ba0bb8c53ae2a9328d1706135b74ec653ed933869b74b6909e63fd"}, - {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667c132f1f3651c14522a119e4dd631fad98761fa960c55e8e7430bb2a1ba4ac"}, - {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42e8961196af655bb5e63ce6c60d25e8798cd4dfbc04f4203457fa3869322c2e"}, - {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75412ca06e20904c19170f8a24486c4e6c7887dea591ba18a1ab572f1300ee9f"}, - {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6af8680328c69e15324b5af3ae38abbfcf9cbec37b5346ebfd52339c3d7e8a18"}, - {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a86fe4ff4ea523eac8f4b57fdac319faf037d3c1be12405e6a7e86b3fbc4756a"}, - {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e607b49b1a106ee2086633167033afbd63f76f2999e9236f638b06b112b24ea7"}, - {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7339f41c244d0eea251637727f016b3d20050636695bc78345cce9029b189401"}, - {file = "orjson-3.11.5-cp310-cp310-win32.whl", hash = "sha256:8be318da8413cdbbce77b8c5fac8d13f6eb0f0db41b30bb598631412619572e8"}, - {file = "orjson-3.11.5-cp310-cp310-win_amd64.whl", hash = "sha256:b9f86d69ae822cabc2a0f6c099b43e8733dda788405cba2665595b7e8dd8d167"}, - {file = "orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8"}, - {file = "orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc"}, - {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968"}, - {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7"}, - {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd"}, - {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9"}, - {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef"}, - {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9"}, - {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125"}, - {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814"}, - {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5"}, - {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880"}, - {file = "orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d"}, - {file = "orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1"}, - {file = "orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c"}, - {file = "orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d"}, - {file = "orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626"}, - {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f"}, - {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85"}, - {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9"}, - {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626"}, - {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa"}, - {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477"}, - {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e"}, - {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69"}, - {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3"}, - {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca"}, - {file = "orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98"}, - {file = "orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875"}, - {file = "orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe"}, - {file = "orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629"}, - {file = "orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3"}, - {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39"}, - {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f"}, - {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51"}, - {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8"}, - {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706"}, - {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f"}, - {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863"}, - {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228"}, - {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2"}, - {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05"}, - {file = "orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef"}, - {file = "orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583"}, - {file = "orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287"}, - {file = "orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0"}, - {file = "orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81"}, - {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f"}, - {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e"}, - {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7"}, - {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb"}, - {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4"}, - {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad"}, - {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829"}, - {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac"}, - {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d"}, - {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439"}, - {file = "orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499"}, - {file = "orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310"}, - {file = "orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5"}, - {file = "orjson-3.11.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1b280e2d2d284a6713b0cfec7b08918ebe57df23e3f76b27586197afca3cb1e9"}, - {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d8a112b274fae8c5f0f01954cb0480137072c271f3f4958127b010dfefaec"}, - {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0a2ae6f09ac7bd47d2d5a5305c1d9ed08ac057cda55bb0a49fa506f0d2da00"}, - {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d87bd1896faac0d10b4f849016db81a63e4ec5df38757ffae84d45ab38aa71"}, - {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:801a821e8e6099b8c459ac7540b3c32dba6013437c57fdcaec205b169754f38c"}, - {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a0f6ac618c98c74b7fbc8c0172ba86f9e01dbf9f62aa0b1776c2231a7bffe5"}, - {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea7339bdd22e6f1060c55ac31b6a755d86a5b2ad3657f2669ec243f8e3b2bdb"}, - {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4dad582bc93cef8f26513e12771e76385a7e6187fd713157e971c784112aad56"}, - {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0522003e9f7fba91982e83a97fec0708f5a714c96c4209db7104e6b9d132f111"}, - {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7403851e430a478440ecc1258bcbacbfbd8175f9ac1e39031a7121dd0de05ff8"}, - {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5f691263425d3177977c8d1dd896cde7b98d93cbf390b2544a090675e83a6a0a"}, - {file = "orjson-3.11.5-cp39-cp39-win32.whl", hash = "sha256:61026196a1c4b968e1b1e540563e277843082e9e97d78afa03eb89315af531f1"}, - {file = "orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30"}, - {file = "orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5"}, -] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pandas" -version = "2.2.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "platformdirs" -version = "4.3.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "proto-plus" -version = "1.26.1" -description = "Beautiful, Pythonic protocol buffers" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, - {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<7.0.0" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "6.33.2" -description = "" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d"}, - {file = "protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4"}, - {file = "protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43"}, - {file = "protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e"}, - {file = "protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872"}, - {file = "protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f"}, - {file = "protobuf-6.33.2-cp39-cp39-win32.whl", hash = "sha256:7109dcc38a680d033ffb8bf896727423528db9163be1b6a02d6a49606dcadbfe"}, - {file = "protobuf-6.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:2981c58f582f44b6b13173e12bb8656711189c2a70250845f264b877f00b1913"}, - {file = "protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c"}, - {file = "protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4"}, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, - {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, -] - -[package.dependencies] -pyasn1 = ">=0.6.1,<0.7.0" - -[[package]] -name = "pybreaker" -version = "1.4.1" -description = "Python implementation of the Circuit Breaker pattern" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pybreaker-1.4.1-py3-none-any.whl", hash = "sha256:b4dab4a05195b7f2a64a6c1a6c4ba7a96534ef56ea7210e6bcb59f28897160e0"}, - {file = "pybreaker-1.4.1.tar.gz", hash = "sha256:8df2d245c73ba40c8242c56ffb4f12138fbadc23e296224740c2028ea9dc1178"}, -] - -[package.extras] -test = ["fakeredis", "mock", "pytest", "redis", "tornado", "types-mock", "types-redis"] - -[[package]] -name = "pycparser" -version = "2.23" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"" -files = [ - {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, - {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, -] - -[[package]] -name = "pydantic" -version = "2.11.10" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, - {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.33.2" -typing-extensions = ">=4.12.2" -typing-inspection = ">=0.4.0" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] - -[[package]] -name = "pydantic-core" -version = "2.33.2" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, - {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.10.1" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, - {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, -] - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pyrate-limiter" -version = "3.1.1" -description = "Python Rate-Limiter using Leaky-Bucket Algorithm" -optional = false -python-versions = ">=3.8,<4.0" -groups = ["main"] -files = [ - {file = "pyrate_limiter-3.1.1-py3-none-any.whl", hash = "sha256:c51906f1d51d56dc992ff6c26e8300e32151bc6cfa3e6559792e31971dfd4e2b"}, - {file = "pyrate_limiter-3.1.1.tar.gz", hash = "sha256:2f57eda712687e6eccddf6afe8f8a15b409b97ed675fe64a626058f12863b7b7"}, -] - -[package.extras] -all = ["filelock (>=3.0)", "redis (>=5.0.0,<6.0.0)"] -docs = ["furo (>=2022.3.4,<2023.0.0)", "myst-parser (>=0.17)", "sphinx (>=4.3.0,<5.0.0)", "sphinx-autodoc-typehints (>=1.17,<2.0)", "sphinx-copybutton (>=0.5)", "sphinxcontrib-apidoc (>=0.3,<0.4)"] - -[[package]] -name = "pytest" -version = "8.3.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-ulid" -version = "3.1.0" -description = "Universally unique lexicographically sortable identifier" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "python_ulid-3.1.0-py3-none-any.whl", hash = "sha256:e2cdc979c8c877029b4b7a38a6fba3bc4578e4f109a308419ff4d3ccf0a46619"}, - {file = "python_ulid-3.1.0.tar.gz", hash = "sha256:ff0410a598bc5f6b01b602851a3296ede6f91389f913a5d5f8c496003836f636"}, -] - -[package.extras] -pydantic = ["pydantic (>=2.0)"] - -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "rapidfuzz" -version = "3.14.3" -description = "rapid fuzzy string matching" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9fcd4d751a4fffa17aed1dde41647923c72c74af02459ad1222e3b0022da3a1"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ad73afb688b36864a8d9b7344a9cf6da186c471e5790cbf541a635ee0f457f2"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5fb2d978a601820d2cfd111e2c221a9a7bfdf84b41a3ccbb96ceef29f2f1ac7"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d83b8b712fa37e06d59f29a4b49e2e9e8635e908fbc21552fe4d1163db9d2a1"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:dc8c07801df5206b81ed6bd6c35cb520cf9b6c64b9b0d19d699f8633dc942897"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c71ce6d4231e5ef2e33caa952bfe671cb9fd42e2afb11952df9fad41d5c821f9"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0e38828d1381a0cceb8a4831212b2f673d46f5129a1897b0451c883eaf4a1747"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da2a007434323904719158e50f3076a4dadb176ce43df28ed14610c773cc9825"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-win32.whl", hash = "sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:37d3c653af15cd88592633e942f5407cb4c64184efab163c40fcebad05f25141"}, - {file = "rapidfuzz-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:cc594bbcd3c62f647dfac66800f307beaee56b22aaba1c005e9c4c40ed733923"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea2d113e260a5da0c4003e0a5e9fdf24a9dc2bb9eaa43abd030a1e46ce7837d"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6c31a4aa68cfa75d7eede8b0ed24b9e458447db604c2db53f358be9843d81d3"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02821366d928e68ddcb567fed8723dad7ea3a979fada6283e6914d5858674850"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe8df315ab4e6db4e1be72c5170f8e66021acde22cd2f9d04d2058a9fd8162e"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:769f31c60cd79420188fcdb3c823227fc4a6deb35cafec9d14045c7f6743acae"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54fa03062124e73086dae66a3451c553c1e20a39c077fd704dc7154092c34c63"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:834d1e818005ed0d4ae38f6b87b86fad9b0a74085467ece0727d20e15077c094"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:948b00e8476a91f510dd1ec07272efc7d78c275d83b630455559671d4e33b678"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-win32.whl", hash = "sha256:43d0305c36f504232f18ea04e55f2059bb89f169d3119c4ea96a0e15b59e2a91"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:ef6bf930b947bd0735c550683939a032090f1d688dfd8861d6b45307b96fd5c5"}, - {file = "rapidfuzz-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:f3eb0ff3b75d6fdccd40b55e7414bb859a1cda77c52762c9c82b85569f5088e7"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329"}, - {file = "rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c"}, - {file = "rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15"}, - {file = "rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9"}, - {file = "rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c"}, - {file = "rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253"}, - {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7cf174b52cb3ef5d49e45d0a1133b7e7d0ecf770ed01f97ae9962c5c91d97d23"}, - {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:442cba39957a008dfc5bdef21a9c3f4379e30ffb4e41b8555dbaf4887eca9300"}, - {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1faa0f8f76ba75fd7b142c984947c280ef6558b5067af2ae9b8729b0a0f99ede"}, - {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e6eefec45625c634926a9fd46c9e4f31118ac8f3156fff9494422cee45207e6"}, - {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56fefb4382bb12250f164250240b9dd7772e41c5c8ae976fd598a32292449cc5"}, - {file = "rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f"}, -] - -[package.extras] -all = ["numpy"] - -[[package]] -name = "referencing" -version = "0.37.0" -description = "JSON Referencing + Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, - {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -rpds-py = ">=0.7.0" -typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} - -[[package]] -name = "regex" -version = "2025.11.3" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "regex-2025.11.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2b441a4ae2c8049106e8b39973bfbddfb25a179dda2bdb99b0eeb60c40a6a3af"}, - {file = "regex-2025.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2fa2eed3f76677777345d2f81ee89f5de2f5745910e805f7af7386a920fa7313"}, - {file = "regex-2025.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8b4a27eebd684319bdf473d39f1d79eed36bf2cd34bd4465cdb4618d82b3d56"}, - {file = "regex-2025.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cf77eac15bd264986c4a2c63353212c095b40f3affb2bc6b4ef80c4776c1a28"}, - {file = "regex-2025.11.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f9ee819f94c6abfa56ec7b1dbab586f41ebbdc0a57e6524bd5e7f487a878c7"}, - {file = "regex-2025.11.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:838441333bc90b829406d4a03cb4b8bf7656231b84358628b0406d803931ef32"}, - {file = "regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe6d3f0c9e3b7e8c0c694b24d25e677776f5ca26dce46fd6b0489f9c8339391"}, - {file = "regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2ab815eb8a96379a27c3b6157fcb127c8f59c36f043c1678110cea492868f1d5"}, - {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:728a9d2d173a65b62bdc380b7932dd8e74ed4295279a8fe1021204ce210803e7"}, - {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:509dc827f89c15c66a0c216331260d777dd6c81e9a4e4f830e662b0bb296c313"}, - {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:849202cd789e5f3cf5dcc7822c34b502181b4824a65ff20ce82da5524e45e8e9"}, - {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b6f78f98741dcc89607c16b1e9426ee46ce4bf31ac5e6b0d40e81c89f3481ea5"}, - {file = "regex-2025.11.3-cp310-cp310-win32.whl", hash = "sha256:149eb0bba95231fb4f6d37c8f760ec9fa6fabf65bab555e128dde5f2475193ec"}, - {file = "regex-2025.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:ee3a83ce492074c35a74cc76cf8235d49e77b757193a5365ff86e3f2f93db9fd"}, - {file = "regex-2025.11.3-cp310-cp310-win_arm64.whl", hash = "sha256:38af559ad934a7b35147716655d4a2f79fcef2d695ddfe06a06ba40ae631fa7e"}, - {file = "regex-2025.11.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eadade04221641516fa25139273505a1c19f9bf97589a05bc4cfcd8b4a618031"}, - {file = "regex-2025.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feff9e54ec0dd3833d659257f5c3f5322a12eee58ffa360984b716f8b92983f4"}, - {file = "regex-2025.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b30bc921d50365775c09a7ed446359e5c0179e9e2512beec4a60cbcef6ddd50"}, - {file = "regex-2025.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f99be08cfead2020c7ca6e396c13543baea32343b7a9a5780c462e323bd8872f"}, - {file = "regex-2025.11.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6dd329a1b61c0ee95ba95385fb0c07ea0d3fe1a21e1349fa2bec272636217118"}, - {file = "regex-2025.11.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c5238d32f3c5269d9e87be0cf096437b7622b6920f5eac4fd202468aaeb34d2"}, - {file = "regex-2025.11.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10483eefbfb0adb18ee9474498c9a32fcf4e594fbca0543bb94c48bac6183e2e"}, - {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78c2d02bb6e1da0720eedc0bad578049cad3f71050ef8cd065ecc87691bed2b0"}, - {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b49cd2aad93a1790ce9cffb18964f6d3a4b0b3dbdbd5de094b65296fce6e58"}, - {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:885b26aa3ee56433b630502dc3d36ba78d186a00cc535d3806e6bfd9ed3c70ab"}, - {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddd76a9f58e6a00f8772e72cff8ebcff78e022be95edf018766707c730593e1e"}, - {file = "regex-2025.11.3-cp311-cp311-win32.whl", hash = "sha256:3e816cc9aac1cd3cc9a4ec4d860f06d40f994b5c7b4d03b93345f44e08cc68bf"}, - {file = "regex-2025.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:087511f5c8b7dfbe3a03f5d5ad0c2a33861b1fc387f21f6f60825a44865a385a"}, - {file = "regex-2025.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:1ff0d190c7f68ae7769cd0313fe45820ba07ffebfddfaa89cc1eb70827ba0ddc"}, - {file = "regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41"}, - {file = "regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22b29dda7e1f7062a52359fca6e58e548e28c6686f205e780b02ad8ef710de36"}, - {file = "regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a91e4a29938bc1a082cc28fdea44be420bf2bebe2665343029723892eb073e1"}, - {file = "regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b884f4226602ad40c5d55f52bf91a9df30f513864e0054bad40c0e9cf1afb7"}, - {file = "regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e0b11b2b2433d1c39c7c7a30e3f3d0aeeea44c2a8d0bae28f6b95f639927a69"}, - {file = "regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87eb52a81ef58c7ba4d45c3ca74e12aa4b4e77816f72ca25258a85b3ea96cb48"}, - {file = "regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12ab1f5c29b4e93db518f5e3872116b7e9b1646c9f9f426f777b50d44a09e8c"}, - {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7521684c8c7c4f6e88e35ec89680ee1aa8358d3f09d27dfbdf62c446f5d4c695"}, - {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7fe6e5440584e94cc4b3f5f4d98a25e29ca12dccf8873679a635638349831b98"}, - {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8e026094aa12b43f4fd74576714e987803a315c76edb6b098b9809db5de58f74"}, - {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:435bbad13e57eb5606a68443af62bed3556de2f46deb9f7d4237bc2f1c9fb3a0"}, - {file = "regex-2025.11.3-cp312-cp312-win32.whl", hash = "sha256:3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204"}, - {file = "regex-2025.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9"}, - {file = "regex-2025.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26"}, - {file = "regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c1e448051717a334891f2b9a620fe36776ebf3dd8ec46a0b877c8ae69575feb4"}, - {file = "regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b5aca4d5dfd7fbfbfbdaf44850fcc7709a01146a797536a8f84952e940cca76"}, - {file = "regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:04d2765516395cf7dda331a244a3282c0f5ae96075f728629287dfa6f76ba70a"}, - {file = "regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d9903ca42bfeec4cebedba8022a7c97ad2aab22e09573ce9976ba01b65e4361"}, - {file = "regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:639431bdc89d6429f6721625e8129413980ccd62e9d3f496be618a41d205f160"}, - {file = "regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f117efad42068f9715677c8523ed2be1518116d1c49b1dd17987716695181efe"}, - {file = "regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4aecb6f461316adf9f1f0f6a4a1a3d79e045f9b71ec76055a791affa3b285850"}, - {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b3a5f320136873cc5561098dfab677eea139521cb9a9e8db98b7e64aef44cbc"}, - {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75fa6f0056e7efb1f42a1c34e58be24072cb9e61a601340cc1196ae92326a4f9"}, - {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:dbe6095001465294f13f1adcd3311e50dd84e5a71525f20a10bd16689c61ce0b"}, - {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:454d9b4ae7881afbc25015b8627c16d88a597479b9dea82b8c6e7e2e07240dc7"}, - {file = "regex-2025.11.3-cp313-cp313-win32.whl", hash = "sha256:28ba4d69171fc6e9896337d4fc63a43660002b7da53fc15ac992abcf3410917c"}, - {file = "regex-2025.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5"}, - {file = "regex-2025.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:2292cd5a90dab247f9abe892ac584cb24f0f54680c73fcb4a7493c66c2bf2467"}, - {file = "regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1eb1ebf6822b756c723e09f5186473d93236c06c579d2cc0671a722d2ab14281"}, - {file = "regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1e00ec2970aab10dc5db34af535f21fcf32b4a31d99e34963419636e2f85ae39"}, - {file = "regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a4cb042b615245d5ff9b3794f56be4138b5adc35a4166014d31d1814744148c7"}, - {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44f264d4bf02f3176467d90b294d59bf1db9fe53c141ff772f27a8b456b2a9ed"}, - {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7be0277469bf3bd7a34a9c57c1b6a724532a0d235cd0dc4e7f4316f982c28b19"}, - {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0d31e08426ff4b5b650f68839f5af51a92a5b51abd8554a60c2fbc7c71f25d0b"}, - {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e43586ce5bd28f9f285a6e729466841368c4a0353f6fd08d4ce4630843d3648a"}, - {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0f9397d561a4c16829d4e6ff75202c1c08b68a3bdbfe29dbfcdb31c9830907c6"}, - {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:dd16e78eb18ffdb25ee33a0682d17912e8cc8a770e885aeee95020046128f1ce"}, - {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:ffcca5b9efe948ba0661e9df0fa50d2bc4b097c70b9810212d6b62f05d83b2dd"}, - {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c56b4d162ca2b43318ac671c65bd4d563e841a694ac70e1a976ac38fcf4ca1d2"}, - {file = "regex-2025.11.3-cp313-cp313t-win32.whl", hash = "sha256:9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a"}, - {file = "regex-2025.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c"}, - {file = "regex-2025.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e"}, - {file = "regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6"}, - {file = "regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4"}, - {file = "regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73"}, - {file = "regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f"}, - {file = "regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d"}, - {file = "regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be"}, - {file = "regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db"}, - {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62"}, - {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f"}, - {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02"}, - {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed"}, - {file = "regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4"}, - {file = "regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad"}, - {file = "regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f"}, - {file = "regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc"}, - {file = "regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49"}, - {file = "regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536"}, - {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95"}, - {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009"}, - {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9"}, - {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d"}, - {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6"}, - {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154"}, - {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267"}, - {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379"}, - {file = "regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38"}, - {file = "regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de"}, - {file = "regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801"}, - {file = "regex-2025.11.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:81519e25707fc076978c6143b81ea3dc853f176895af05bf7ec51effe818aeec"}, - {file = "regex-2025.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3bf28b1873a8af8bbb58c26cc56ea6e534d80053b41fb511a35795b6de507e6a"}, - {file = "regex-2025.11.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:856a25c73b697f2ce2a24e7968285579e62577a048526161a2c0f53090bea9f9"}, - {file = "regex-2025.11.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a3d571bd95fade53c86c0517f859477ff3a93c3fde10c9e669086f038e0f207"}, - {file = "regex-2025.11.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:732aea6de26051af97b94bc98ed86448821f839d058e5d259c72bf6d73ad0fc0"}, - {file = "regex-2025.11.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:51c1c1847128238f54930edb8805b660305dca164645a9fd29243f5610beea34"}, - {file = "regex-2025.11.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22dd622a402aad4558277305350699b2be14bc59f64d64ae1d928ce7d072dced"}, - {file = "regex-2025.11.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f3b5a391c7597ffa96b41bd5cbd2ed0305f515fcbb367dfa72735679d5502364"}, - {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cc4076a5b4f36d849fd709284b4a3b112326652f3b0466f04002a6c15a0c96c1"}, - {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a295ca2bba5c1c885826ce3125fa0b9f702a1be547d821c01d65f199e10c01e2"}, - {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b4774ff32f18e0504bfc4e59a3e71e18d83bc1e171a3c8ed75013958a03b2f14"}, - {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e7d1cdfa88ef33a2ae6aa0d707f9255eb286ffbd90045f1088246833223aee"}, - {file = "regex-2025.11.3-cp39-cp39-win32.whl", hash = "sha256:74d04244852ff73b32eeede4f76f51c5bcf44bc3c207bc3e6cf1c5c45b890708"}, - {file = "regex-2025.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:7a50cd39f73faa34ec18d6720ee25ef10c4c1839514186fcda658a06c06057a2"}, - {file = "regex-2025.11.3-cp39-cp39-win_arm64.whl", hash = "sha256:43b4fb020e779ca81c1b5255015fe2b82816c76ec982354534ad9ec09ad7c9e3"}, - {file = "regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01"}, -] - -[[package]] -name = "requests" -version = "2.32.5" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-cache" -version = "1.2.1" -description = "A persistent cache for python requests" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, - {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, -] - -[package.dependencies] -attrs = ">=21.2" -cattrs = ">=22.2" -platformdirs = ">=2.5" -requests = ">=2.22" -url-normalize = ">=1.4" -urllib3 = ">=1.25.5" - -[package.extras] -all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=6.0.1)", "redis (>=3)", "ujson (>=5.4)"] -bson = ["bson (>=0.5)"] -docs = ["furo (>=2023.3,<2024.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.9)"] -dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] -json = ["ujson (>=5.4)"] -mongodb = ["pymongo (>=3)"] -redis = ["redis (>=3)"] -security = ["itsdangerous (>=2.0)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "rich" -version = "14.2.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, - {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rich-click" -version = "1.9.4" -description = "Format click help output nicely with rich" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "rich_click-1.9.4-py3-none-any.whl", hash = "sha256:d70f39938bcecaf5543e8750828cbea94ef51853f7d0e174cda1e10543767389"}, - {file = "rich_click-1.9.4.tar.gz", hash = "sha256:af73dc68e85f3bebb80ce302a642b9fe3b65f3df0ceb42eb9a27c467c1b678c8"}, -] - -[package.dependencies] -click = ">=8" -colorama = {version = "*", markers = "platform_system == \"Windows\""} -rich = ">=12" -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["inline-snapshot (>=0.24)", "jsonschema (>=4)", "mypy (>=1.14.1)", "nodeenv (>=1.9.1)", "packaging (>=25)", "pre-commit (>=3.5)", "pytest (>=8.3.5)", "pytest-cov (>=5)", "rich-codex (>=1.2.11)", "ruff (>=0.12.4)", "typer (>=0.15)", "types-setuptools (>=75.8.0.20250110)"] -docs = ["markdown-include (>=0.8.1)", "mike (>=2.1.3)", "mkdocs-github-admonitions-plugin (>=0.1.1)", "mkdocs-glightbox (>=0.4)", "mkdocs-include-markdown-plugin (>=7.1.7) ; python_version >= \"3.9\"", "mkdocs-material-extensions (>=1.3.1)", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-redirects (>=1.2.2)", "mkdocs-rss-plugin (>=1.15)", "mkdocs[docs] (>=1.6.1)", "mkdocstrings[python] (>=0.26.1)", "rich-codex (>=1.2.11)", "typer (>=0.15)"] - -[[package]] -name = "rpds-py" -version = "0.30.0" -description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, - {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, - {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, - {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, - {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, - {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, - {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, - {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, - {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, -] - -[[package]] -name = "rsa" -version = "4.9.1" -description = "Pure-Python RSA implementation" -optional = false -python-versions = "<4,>=3.6" -groups = ["main"] -files = [ - {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, - {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "serpyco-rs" -version = "1.17.1" -description = "" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "serpyco_rs-1.17.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:400f3a6b3fe25b4dacf16171603e8a845d78da0660e4aecf6c858a34fcf4b6c2"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6bf8485e4e591b0242bcc016d58d43b2eb4f96311f40f402726d499cfec9266"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50204f3268ef6ab752ab605c5a89bdd4a85a0652e77d201c9c3bc57d8b635d6e"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f9d897dd3703e0aa13e4aa61d9645372a7dc1509bc7af08cbbecc5741c223ac8"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e5724c68d3407b84709ece543420ceae054bd2e8052a994b9f975bba05a14df"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8262703337272f65293dba092f576893485670348f8e9aec58e02e5164c3e4d0"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9c2d7d738adff1a847650cdc2e6def1827c7289da14a743f5bcfa5f2aad597d"}, - {file = "serpyco_rs-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:566c67defaea2d280cd5bfa6d250b4ade507f62559b17a275628a9b63c6804e7"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6c6bd6f3a63a70e2a57091e4e79d67aea0a99c806e0ede9bbf3f8cfe29f0ae2c"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31bcaf64475d990c60e07620261b50a1c3fd42aeceba39cefc06e5e3bcebe191"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7483d3427505608d322977028fb85dd701d2cc889c5d41e6a9fbf390d3b63ab3"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0e9546d1208a714cfe6c08b6a5f5ffe235db1791f6b313d09f7d16f7dc0e89be"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0da8b8ac02f3b0b2d56a543bc7036c6fe7179b235502215ecb77ccea5f62a1b3"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2eeccfcca8755ee97d43a08cda1c915c3594bf06bbf68d9eefd26162fe1417b8"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f708f77de501fc795841d66da850e7fbf6f01366b875c5cf84b6d00e86f80f1"}, - {file = "serpyco_rs-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ded1bfe1b46671b0c7677a6c6691604910f1a575e9aecc0298484ddffdc5c9ca"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:68a24477f87eb169023b39fc4050165fb16cb4505b334050f51e6b00604678f0"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c37f259255d2c988617ef0ce723b144a9df960a042d1058754ba224e0e54ce9c"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37a697cf0da282e948755de04bd6faf3a7dc410517c0c829260db64b98b1285"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:478007504b166cb02be110b6ebfe9f056119ca43c52758af5ffe7eb32c74360d"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de3c5a11299c3e36c4064fc6ca3908cdbb3e261c7d6879f9049bfab3fb81cfc9"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:964735c0e214a9248b6f8bee315880b3b844b948e26822b426becef078821daf"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e732591ec48746edc2ddd43df35ab82ebaca507bb8f9fb7bd7db0f8b5018fc2e"}, - {file = "serpyco_rs-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d3b01b247aabba9fe7d60806d9c65d8af67c0d8f0c2bc945a23dce9094c4ddd"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f0247812fa0a7299d8235e9c7b6a981eccdb05a62339a192e6814f2798f5e736"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee6ffc6e98fd4bd4342ecbbf71d2fd6a83a516061ebfeca341459091a1d32e8"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:192b0aaf22256a5c174e9ac58b483ee52e69897f8914b6c8d18e7fa5dfc3c98c"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0f9f1863de8ed37f25fb12794d9c2ae19487e0cd50bb36c54eb323f690239dad"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffe3079fa212235382d40f6b550204b97cc9122d917c189a246babf5ce3ffae"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3f63c6678079b9c288804e68af684e7cfe9119f9e7fced11b7baade2436d69e"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67d7bdda66cbb2d8e6986fc33ed85034baa30add209f41dc2fde9dfc0997c88"}, - {file = "serpyco_rs-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a9ef8caa1778778ee4f14906326dbb34409dbdd7a2d784efd2a1a09c0621478"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d74dde9ebb0cb0d79885199da6ac3ba5281d32a026577d0272ce0a3b1201ceb"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89e7dfaf6a5923e25389cfa93ac3c62c50db36afc128d8184ab511406df309e"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e937777c7a3e46702d9c0e8cfa5b6be5262662c6e30bff6fd7fc021c011819c"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:046afe7effed2b636f603b7d2099e4e97f6ef64cbbd9e1c5402db56bcc34bda9"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09ee2324c92c065bcd5ed620d34a6d1cf089befba448cf9f91dd165f635f9926"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a09edfc74729f0265762c1e1169d22f2c78106206c1739320edfdf86f472e7b"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31742c518aeb4d142275faf714ce0008fbede8af5907ac819097bd6a15431fd"}, - {file = "serpyco_rs-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:868743b64d979bff61769b94d991bc85d30086600b1fd2e0cc872ec269d40d77"}, - {file = "serpyco_rs-1.17.1.tar.gz", hash = "sha256:548d8f4d13f31363eba0f10e8c5240f007f9059566badc0b8cf9429fd89deb48"}, -] - -[package.dependencies] -attributes-doc = "*" -typing-extensions = "*" - -[[package]] -name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "thrift" -version = "0.20.0" -description = "Python bindings for the Apache Thrift RPC system" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "thrift-0.20.0.tar.gz", hash = "sha256:4dd662eadf6b8aebe8a41729527bd69adf6ceaa2a8681cbef64d1273b3e8feba"}, -] - -[package.dependencies] -six = ">=1.7.2" - -[package.extras] -all = ["tornado (>=4.0)", "twisted"] -tornado = ["tornado (>=4.0)"] -twisted = ["twisted"] - -[[package]] -name = "tomli" -version = "2.1.0" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, -] - -[[package]] -name = "tqdm" -version = "4.67.1" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, - {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[[package]] -name = "tzdata" -version = "2024.2" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["main"] -files = [ - {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, - {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, -] - -[[package]] -name = "tzlocal" -version = "5.3.1" -description = "tzinfo object for the local timezone" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d"}, - {file = "tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd"}, -] - -[package.dependencies] -tzdata = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] - -[[package]] -name = "unidecode" -version = "1.4.0" -description = "ASCII transliterations of Unicode text" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021"}, - {file = "Unidecode-1.4.0.tar.gz", hash = "sha256:ce35985008338b676573023acc382d62c264f307c8f7963733405add37ea2b23"}, -] - -[[package]] -name = "url-normalize" -version = "1.4.3" -description = "URL normalization for Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -groups = ["main"] -files = [ - {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, - {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wcmatch" -version = "10.0" -description = "Wildcard/glob file name matcher." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, - {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, -] - -[package.dependencies] -bracex = ">=2.1.1" - -[[package]] -name = "whenever" -version = "0.8.10" -description = "Modern datetime library for Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "whenever-0.8.10-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d9ecb6b649cb7e5c85742f626ddd56d5cf5d276c632a47ec5d72714350300564"}, - {file = "whenever-0.8.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0698cbd2209413f7a0cb84507405587e7b3995ce22504e50477a1a65ec3b65b9"}, - {file = "whenever-0.8.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30b2f25ee740f5d201f643982c50f0d6ba2fdbb69704630467d85286e290fdab"}, - {file = "whenever-0.8.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb6abd25e03e1aaa9c4ab949c1b02d755be6ea2f18d6a86e0d024a66705beec6"}, - {file = "whenever-0.8.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:228860bfc14e63b7c2c6980e41dee7f4efb397accc06eabc51e9dfeaf633ad5a"}, - {file = "whenever-0.8.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0af24862ded1dcb71e096e7570e6e031f934e7cfa57123363ef21049f8f9fdd4"}, - {file = "whenever-0.8.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6331ebf85dd234d33fdd627146f20808c6eb39f8056dbd09715055f21cd7c494"}, - {file = "whenever-0.8.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ce5dfa7769444e12ae8f0fba8bdce05a8081e1829a9de68d4cc02a11ff71131"}, - {file = "whenever-0.8.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9768562c5a871b2a6377697eb76943fd798c663a4a96b499e4d2fa69c42d7397"}, - {file = "whenever-0.8.10-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f88d9ec50f2dfa4981924cb87fb287708ccb5f770fd93dd9c6fc27641e686c1c"}, - {file = "whenever-0.8.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:507462b0f02d7d4cdfe90888a0158ee3d6c5d49fa3ddcd1b44901c6778fd7381"}, - {file = "whenever-0.8.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ba2d930b5e428e1b0c01ef6c8af14eb94f84792c37d79352f954cd9ea791838e"}, - {file = "whenever-0.8.10-cp310-cp310-win32.whl", hash = "sha256:b598be861fd711d2df683d32dbb15d05279e2e932a4c31f2f7bfd28196985662"}, - {file = "whenever-0.8.10-cp310-cp310-win_amd64.whl", hash = "sha256:66eab892d56685a84a9d933b8252c68794eede39b5105f20d06b000ff17275d4"}, - {file = "whenever-0.8.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3f03f9bef7e3bfe40461e74c74af0cf8dc90489dacc2360069faccf2997f4bca"}, - {file = "whenever-0.8.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f42eb10aaf2818b0e26a5d5230c6cb735ca109882ec4b19cb5cf646c0d28120"}, - {file = "whenever-0.8.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b3ddb300e32b19dd9af391d98ba62b21288d628ec17acf4752d96443a3174"}, - {file = "whenever-0.8.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:907e7d9fca7dfdaa2fae187320442c1f10d41cadefd1bb58b11b9b30ad36a51f"}, - {file = "whenever-0.8.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:671380d09a5cf7beae203d4fcb03e4434e41604d8f5832bd67bc060675e7ba93"}, - {file = "whenever-0.8.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:816a6ae3b5129afee5ecbac958a828efbad56908db9d6ca4c90cc57133145071"}, - {file = "whenever-0.8.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f5a51878bdf520655d131a50ca03e7b8a20ec249042e26bf76eeef64e79f3cb"}, - {file = "whenever-0.8.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:071fba23f80a3857db6cbe6c449dd2e0f0cea29d4466c960e52699ef3ed126ae"}, - {file = "whenever-0.8.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c50060b2d3561762dc15d742d03b3c1377778b2896d6c6f3824f15f943d12b62"}, - {file = "whenever-0.8.10-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2d1b3d00388ce26f450841c34b513fe963ae473a94e6e9c113a534803a70702b"}, - {file = "whenever-0.8.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9dc6510beda89e520608459da41b10092e770c58b3b472418fec2633c50857d"}, - {file = "whenever-0.8.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08bae07abb1d2cdc017d38451a3cae5b5577b5b875b65f89847516e6380201dd"}, - {file = "whenever-0.8.10-cp311-cp311-win32.whl", hash = "sha256:96fc39933480786efc074f469157e290414d14bae1a6198bb7e44bc6f6b3531a"}, - {file = "whenever-0.8.10-cp311-cp311-win_amd64.whl", hash = "sha256:a5bad9acce99b46f6dd5dc64c2aab62a0ffba8dcdeeebbd462e37431af0bf243"}, - {file = "whenever-0.8.10-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9877982944af2b5055d3aeedcdc3f7af78767f5ce7be8994c3f54b3ffba272e9"}, - {file = "whenever-0.8.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:72db2f4e2511e0c01e63d16a8f539ce82096a08111fa9c63d718c6f49768dce6"}, - {file = "whenever-0.8.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da0e929bcc4aa807a68aa766bf040ae314bb4ad291dcc9e75d9e472b5eccec0f"}, - {file = "whenever-0.8.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c9bea3260edc9018d0c08d20d836fb9d69fdd2dfb25f8f71896de70e1d88c1"}, - {file = "whenever-0.8.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e8c14d7c5418db4e3e52bb4e33138334f86d1c4e6059aa2642325bf5270cc06"}, - {file = "whenever-0.8.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be8156fd0b84b57b52f43f0df41e5bf775df6fce8323f2d69bc0b0a36b08836b"}, - {file = "whenever-0.8.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3381092c1944baff5b80b1e81f63684e365a84274f80145cbd6f07f505725ae2"}, - {file = "whenever-0.8.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0792c5f0f5bea0749fccd3f1612594305ba1e7c3a5173ff096f32895bb3de0d"}, - {file = "whenever-0.8.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:49cca1b92b1dd7da33b7f4f5f699d6c3a376ad8ea293f67c23b2b00df218a3ea"}, - {file = "whenever-0.8.10-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1791288d70931319910860ac4e941d944da3a7c189199dc37a877a9844f8af01"}, - {file = "whenever-0.8.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:162da8253584608100e35b8b6b95a1fe7edced64b13ceac70351d30459425d67"}, - {file = "whenever-0.8.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ce5529a859321c88b25bee659f761447281fe3fbe52352c7c9aa49f0ee8d7ff"}, - {file = "whenever-0.8.10-cp312-cp312-win32.whl", hash = "sha256:7e756ea4c89995e702ca6cfb061c9536fac3395667e1737c23ca7eb7462e6ce7"}, - {file = "whenever-0.8.10-cp312-cp312-win_amd64.whl", hash = "sha256:19c4279bc5907881cbfe310cfe32ba58163ce1c515c056962d121875231be03f"}, - {file = "whenever-0.8.10-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:817270c3081b34c07a555fa6d156b96db9722193935cda97a357c4f1ea65962a"}, - {file = "whenever-0.8.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a25f06c17ff0fcaebedd5770afd74055f6b029207c7a24a043fc02d60474b437"}, - {file = "whenever-0.8.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:171564243baa64c4255692dfe79f4b04728087202d26b381ab9b975e5bc1bfd8"}, - {file = "whenever-0.8.10-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d2bd0cc78575c20ec7c3442713abf318a036cfb14d3968e003005b71be3ad02"}, - {file = "whenever-0.8.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd8e26c3e3fa1a2eba65eb2bb1d2411b5509126576c358c8640f0681d86eec8f"}, - {file = "whenever-0.8.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78418a4740dfd3b81c11cfeca0644bf61050aa4c3418a4f446d73d0dff02bbfc"}, - {file = "whenever-0.8.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dc5d6ec53ddb8013840b2530c5dbc0dcf84e65b0e535b54db74a53d04112fc1"}, - {file = "whenever-0.8.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9fc565c35aa1b8abcc84e6b229936a820091b7e3032be22133225b3eda808fc9"}, - {file = "whenever-0.8.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e82b4607c5c297e71b85abb141c2bcc18e9ab265fa18f5c56b5b88276c16d18"}, - {file = "whenever-0.8.10-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aac1b17c6618f830f40f20625362daed46369e17fafcd7f78afb6717936c4e23"}, - {file = "whenever-0.8.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0f7c297f4d35ded618807c097b741049ade092a8e44c7a2ff07f7107dff58584"}, - {file = "whenever-0.8.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78e367869f94ffee9c89aace9eb3f62bb0a11f018394524dd2a67e9058baa5"}, - {file = "whenever-0.8.10-cp313-cp313-win32.whl", hash = "sha256:a2be0191ca3a4999d7409762b1e5c766f84137cd08963fb21ca2107e8fc45792"}, - {file = "whenever-0.8.10-cp313-cp313-win_amd64.whl", hash = "sha256:5e4f9df18a6e20560999c52a2b408cc0338102c76a34da9c8e232eae00e39f9b"}, - {file = "whenever-0.8.10-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5fe66f538a31ab4e5df7af65d8e91ebaf77a8acc69b927634d5e3cef07f3ec28"}, - {file = "whenever-0.8.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f88bd39e8296542b9d04350a547597e9fbf9ca044b4875eb1bfd927a4d382167"}, - {file = "whenever-0.8.10-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb215aaeac78078c94a640d0daf5d0cedb60cb9c82ffce88b2c453b64f94ac2"}, - {file = "whenever-0.8.10-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9512761620375e2905e2135cd0fadc0b110ab10150d25fc1d67154ce84aae55f"}, - {file = "whenever-0.8.10-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9ab03257c3ce7a13f71e0bcd3e0289e1cb8ce95cf982b0fc36faa0dfcee64be"}, - {file = "whenever-0.8.10-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f19fee1807fc5b93c299e4fb603946b3920fce9a25bd22c93dbb862bddfdd48d"}, - {file = "whenever-0.8.10-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4492104887f91f81ac374ef20b05e4e88c087e9d51ac01013fc2a7b3c1f5bf33"}, - {file = "whenever-0.8.10-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1371004dcd825acc47d7efd50550810041690a8eef01a77da55303fee1b221fa"}, - {file = "whenever-0.8.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:56fbad29ce7b85171567edf1ce019d6bc76f614655cd8c4db00a146cae9f2a6a"}, - {file = "whenever-0.8.10-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f172ca567153e73c6576708cc0c90908c30c65c70a08f7ca2173e2f5c2a22953"}, - {file = "whenever-0.8.10-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c017ff3f4232aa2aeeded63f2a7006a1b628d488e057e979f3591900e0709f55"}, - {file = "whenever-0.8.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5cb94d112d4308ecd75ee811d976463061054ea697250eb661bfef948fe3"}, - {file = "whenever-0.8.10-cp314-cp314-win32.whl", hash = "sha256:ee36bb13a3188f06d32de83373e05bcd41f09521b5aedd31351641f7361a5356"}, - {file = "whenever-0.8.10-cp314-cp314-win_amd64.whl", hash = "sha256:c4353c3bfbc3a4bc0a39ccca84559dfd68900d07dc950b573ccb25892456a1ec"}, - {file = "whenever-0.8.10-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:427499d7a52eb31c9f943ff8febdb3772a8e49cb4b2720769fb718fb5efbacb6"}, - {file = "whenever-0.8.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95b9651fc8f99a53b0a10c2f70715b2b2a94e8371dbf3403a1efa6f0eb80a35e"}, - {file = "whenever-0.8.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87845246ce51fd994b9b67ef3e4444a219c42e67f062b7a8b9be5957fd6afb41"}, - {file = "whenever-0.8.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f94ad2271d1c57d5331af0a891451bf60e484c7c32e3743b733e55975ae6969"}, - {file = "whenever-0.8.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd540aa042db2b076ef42b880794170ee0a1347825472b0b789a688db4bf834"}, - {file = "whenever-0.8.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00a9a6f124e9331e642b21dec609b5e70eb6b9368a8add25dfd41a8976dfe11a"}, - {file = "whenever-0.8.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eefb198263e703ff5bf033eae9d7c5c9ea57f4374f7ed650a8dd4777875a727a"}, - {file = "whenever-0.8.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b7c60a29397c722ca952bd2626a4e3ee822fa1c811f21da67cfd48c4e5e840c"}, - {file = "whenever-0.8.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5af9fd62bfbd6fada0fd8f9a0956e4cb0ac2333dd9425a2da40e28e496e2ea6d"}, - {file = "whenever-0.8.10-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:2655ca181e6178d7516c4f00adb2cf3e31afd9a7b078509a8c639f2897203bb1"}, - {file = "whenever-0.8.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bb974da1d13de1424e813df40b037ae3de214ace56ea28c9812e16b66ac8733e"}, - {file = "whenever-0.8.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ec0555fe74703643880c8ecd5b421b1d446e277a44aba1c36243026976ea0d8d"}, - {file = "whenever-0.8.10-cp39-cp39-win32.whl", hash = "sha256:ad4d66ccddf9ba28e7840bc2d2a7507d3ab4384b6062557dd428b7fc60c1f211"}, - {file = "whenever-0.8.10-cp39-cp39-win_amd64.whl", hash = "sha256:6c5c445587c5f690d6989e11cd1f0825558c22a4bce9dce8bf45151f61612272"}, - {file = "whenever-0.8.10-py3-none-any.whl", hash = "sha256:5393187037cff776fe1f5e0fe6094cb52f4509945459d239b9fcc09d95696f43"}, - {file = "whenever-0.8.10.tar.gz", hash = "sha256:5e2a3da71527e299f98eec5bb38c4e79d9527a127107387456125005884fb235"}, -] - -[package.dependencies] -tzdata = {version = ">=2020.1", markers = "sys_platform == \"win32\""} - -[[package]] -name = "xmltodict" -version = "0.14.2" -description = "Makes working with XML feel like you are working with JSON" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, - {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, -] - -[metadata] -lock-version = "2.1" -python-versions = ">=3.10,<3.14" -content-hash = "787eb90809a2d9c50cc85e0fd79c6934a0fe71b8d23e1569fb7b90a65bc2d3b9" diff --git a/integrations/destination-databricks-py/pyproject.toml b/integrations/destination-databricks-py/pyproject.toml deleted file mode 100644 index 738ebe6..0000000 --- a/integrations/destination-databricks-py/pyproject.toml +++ /dev/null @@ -1,28 +0,0 @@ -[build-system] -requires = [ "poetry-core>=1.0.0",] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -version = "0.1.0" -name = "destination-databricks" -description = "Destination implementation for Databricks." -authors = [ "Sri ",] -license = "MIT" -readme = "README.md" -documentation = "https://docs.airbyte.com/integrations/destinations/databricks" -homepage = "https://airbyte.com" -repository = "https://github.com/airbytehq/airbyte" -[[tool.poetry.packages]] -include = "destination_databricks" - -[tool.poetry.dependencies] -python = ">=3.10,<3.14" -databricks-sql-connector = "==4.2.2" -airbyte-cdk = "==7.6.0" -requests = ">=2.32.0" - -[tool.poetry.scripts] -destination-databricks = "destination_databricks.run:run" - -[tool.poetry.group.dev.dependencies] -pytest = "^8.3.2" diff --git a/notebooks/_setup.py b/notebooks/_setup.py index 92deb2b..65910c5 100644 --- a/notebooks/_setup.py +++ b/notebooks/_setup.py @@ -1,8 +1,7 @@ # Databricks notebook source -# MAGIC %pip install airbyte +# MAGIC %pip install airbyte databricks-sdk databricks-sql-connector virtualenv pyarrow # MAGIC %pip install git+https://github.com/park-peter/brickbyte.git --force-reinstall --no-deps # COMMAND ---------- dbutils.library.restartPython() - diff --git a/notebooks/brickbyte-azure-blob.py b/notebooks/brickbyte-azure-blob.py new file mode 100644 index 0000000..f363b32 --- /dev/null +++ b/notebooks/brickbyte-azure-blob.py @@ -0,0 +1,226 @@ +# Databricks notebook source +# MAGIC %run ./_setup + +# COMMAND ---------- + +# MAGIC %md +# MAGIC # Azure Blob Storage to Databricks with BrickByte +# MAGIC +# MAGIC This notebook syncs files from Azure Blob Storage to Delta Lake tables in Unity Catalog. +# MAGIC +# MAGIC **Supported file formats:** CSV, JSON, Parquet, Avro, JSONL +# MAGIC +# MAGIC **Authentication options:** +# MAGIC - Storage Account Key +# MAGIC - Service Principal (Client Credentials) +# MAGIC - OAuth 2.0 +# MAGIC +# MAGIC **Prerequisites:** +# MAGIC - Azure Storage Account name +# MAGIC - Container (bucket) name +# MAGIC - Authentication credentials + +# COMMAND ---------- + +from brickbyte import Brickbyte + +bb = Brickbyte() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 1: Authenticate with Storage Account Key + +# COMMAND ---------- + +# Store credentials in Databricks Secrets for production: +# account_key = dbutils.secrets.get(scope="azure", key="storage_account_key") + +result = bb.sync( + source="source-azure-blob-storage", + source_config={ + "azure_blob_storage_account_name": "your_storage_account", + "azure_blob_storage_container_name": "your_container", + "credentials": { + "auth_type": "storage_account_key", + "azure_blob_storage_account_key": "YOUR_STORAGE_ACCOUNT_KEY", + }, + "streams": [ + { + "name": "csv_files", + "globs": ["**/*.csv"], + "format": {"filetype": "csv"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 2: Authenticate with Service Principal (Client Credentials) +# MAGIC +# MAGIC Requires IAM role `Storage Blob Data Reader` assigned to the Service Principal. + +# COMMAND ---------- + +result = bb.sync( + source="source-azure-blob-storage", + source_config={ + "azure_blob_storage_account_name": "your_storage_account", + "azure_blob_storage_container_name": "your_container", + "credentials": { + "auth_type": "client_credentials", + "tenant_id": "YOUR_TENANT_ID", + "client_id": "YOUR_CLIENT_ID", + "client_secret": "YOUR_CLIENT_SECRET", + }, + "streams": [ + { + "name": "parquet_files", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync JSON Lines Files + +# COMMAND ---------- + +result = bb.sync( + source="source-azure-blob-storage", + source_config={ + "azure_blob_storage_account_name": "your_storage_account", + "azure_blob_storage_container_name": "your_container", + "credentials": { + "auth_type": "storage_account_key", + "azure_blob_storage_account_key": "YOUR_KEY", + }, + "streams": [ + { + "name": "json_data", + "globs": ["data/**/*.jsonl", "logs/**/*.json"], + "format": {"filetype": "jsonl"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Avro Files + +# COMMAND ---------- + +result = bb.sync( + source="source-azure-blob-storage", + source_config={ + "azure_blob_storage_account_name": "your_storage_account", + "azure_blob_storage_container_name": "your_container", + "credentials": { + "auth_type": "storage_account_key", + "azure_blob_storage_account_key": "YOUR_KEY", + }, + "streams": [ + { + "name": "avro_data", + "globs": ["**/*.avro"], + "format": {"filetype": "avro"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Multiple Streams (Different File Types) + +# COMMAND ---------- + +result = bb.sync( + source="source-azure-blob-storage", + source_config={ + "azure_blob_storage_account_name": "your_storage_account", + "azure_blob_storage_container_name": "your_container", + "credentials": { + "auth_type": "storage_account_key", + "azure_blob_storage_account_key": "YOUR_KEY", + }, + "streams": [ + { + "name": "sales_csv", + "globs": ["sales/**/*.csv"], + "format": {"filetype": "csv"}, + }, + { + "name": "events_json", + "globs": ["events/**/*.jsonl"], + "format": {"filetype": "jsonl"}, + }, + { + "name": "analytics_parquet", + "globs": ["analytics/**/*.parquet"], + "format": {"filetype": "parquet"}, + }, + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from streams: {result.streams_synced}") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With AI Metadata Enrichment + +# COMMAND ---------- + +# result = bb.sync( +# source="source-azure-blob-storage", +# source_config={ +# "azure_blob_storage_account_name": "your_storage_account", +# "azure_blob_storage_container_name": "your_container", +# "credentials": { +# "auth_type": "storage_account_key", +# "azure_blob_storage_account_key": "YOUR_KEY", +# }, +# "streams": [ +# { +# "name": "customer_data", +# "globs": ["customers/**/*.csv"], +# "format": {"filetype": "csv"}, +# } +# ], +# }, +# catalog="", # TODO: Set your Unity Catalog name +# schema="", # TODO: Set your target schema +# enrich_metadata=True, +# enrich_model="databricks-meta-llama-3-3-70b-instruct", +# ) + +# print(f"Enriched tables: {result.enriched_tables}") diff --git a/notebooks/brickbyte-confluence.ipynb b/notebooks/brickbyte-confluence.ipynb deleted file mode 100644 index 0519ecb..0000000 --- a/notebooks/brickbyte-confluence.ipynb +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/notebooks/brickbyte-confluence.py b/notebooks/brickbyte-confluence.py index 2671ea5..02e7624 100644 --- a/notebooks/brickbyte-confluence.py +++ b/notebooks/brickbyte-confluence.py @@ -2,7 +2,7 @@ # MAGIC %md # MAGIC # BrickByte - Confluence Example # MAGIC -# MAGIC Sync data from Atlassian Confluence to Databricks in one line. +# MAGIC Sync data from Atlassian Confluence to Databricks. # MAGIC # MAGIC ## Prerequisites # MAGIC - Confluence Cloud account @@ -14,10 +14,13 @@ # COMMAND ---------- -from brickbyte import BrickByte +from brickbyte import Brickbyte -bb = BrickByte() +bb = Brickbyte() +# COMMAND ---------- + +# Simple sync result = bb.sync( source="source-confluence", source_config={ @@ -27,6 +30,7 @@ }, catalog="", # TODO: Set your Unity Catalog name schema="", # TODO: Set your target schema + # staging_volume="", # Optional: Set if running outside of Databricks Notebooks # streams=["pages", "spaces"], # Optional: select specific streams ) @@ -42,6 +46,6 @@ # MAGIC _airbyte_data:id AS page_id, # MAGIC _airbyte_data:title AS title, # MAGIC _airbyte_data:status AS status -# MAGIC FROM your_catalog.your_schema._airbyte_raw_pages +# MAGIC FROM your_catalog.your_schema.pages # MAGIC LIMIT 10; # MAGIC ``` diff --git a/notebooks/brickbyte-datadog.ipynb b/notebooks/brickbyte-datadog.ipynb deleted file mode 100644 index 0519ecb..0000000 --- a/notebooks/brickbyte-datadog.ipynb +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/notebooks/brickbyte-datadog.py b/notebooks/brickbyte-datadog.py index 035f36d..61b004e 100644 --- a/notebooks/brickbyte-datadog.py +++ b/notebooks/brickbyte-datadog.py @@ -2,7 +2,7 @@ # MAGIC %md # MAGIC # BrickByte - DataDog Example # MAGIC -# MAGIC Sync monitoring data from DataDog to Databricks in one line. +# MAGIC Sync monitoring data from DataDog to Databricks. # MAGIC # MAGIC ## Prerequisites # MAGIC - DataDog API Key (https://app.datadoghq.com/organization-settings/api-keys) @@ -14,10 +14,13 @@ # COMMAND ---------- -from brickbyte import BrickByte +from brickbyte import Brickbyte -bb = BrickByte() +bb = Brickbyte() +# COMMAND ---------- + +# Simple sync result = bb.sync( source="source-datadog", source_config={ @@ -29,6 +32,7 @@ }, catalog="", # TODO: Set your Unity Catalog name schema="", # TODO: Set your target schema + # staging_volume="", # Optional: Set if running outside of Databricks Notebooks # streams=["monitors", "dashboards"], # Optional: select specific streams ) @@ -45,7 +49,7 @@ # MAGIC _airbyte_data:id AS monitor_id, # MAGIC _airbyte_data:name AS name, # MAGIC _airbyte_data:overall_state AS status -# MAGIC FROM your_catalog.your_schema._airbyte_raw_monitors +# MAGIC FROM your_catalog.your_schema.monitors # MAGIC LIMIT 20; # MAGIC # MAGIC -- View dashboards @@ -53,6 +57,6 @@ # MAGIC _airbyte_data:id AS dashboard_id, # MAGIC _airbyte_data:title AS title, # MAGIC _airbyte_data:author_handle AS author -# MAGIC FROM your_catalog.your_schema._airbyte_raw_dashboards +# MAGIC FROM your_catalog.your_schema.dashboards # MAGIC LIMIT 20; # MAGIC ``` diff --git a/notebooks/brickbyte-example.py b/notebooks/brickbyte-example.py index 4809745..8ea1c4d 100644 --- a/notebooks/brickbyte-example.py +++ b/notebooks/brickbyte-example.py @@ -3,10 +3,41 @@ # COMMAND ---------- -from brickbyte import BrickByte +# MAGIC %md +# MAGIC # BrickByte Quick Start +# MAGIC +# MAGIC BrickByte bridges Airbyte's 600+ connectors directly into Databricks. +# MAGIC +# MAGIC ## Credential Management +# MAGIC +# MAGIC BrickByte automatically discovers credentials from **Databricks Secrets**: +# MAGIC +# MAGIC | Scope | Key Pattern | Example | +# MAGIC |-------|-------------|---------| +# MAGIC | `brickbyte` | `{source-name}/{field}` | `source-s3/aws_access_key_id` | +# MAGIC +# MAGIC **Setup your secrets once:** +# MAGIC ``` +# MAGIC databricks secrets put-secret brickbyte source-s3/aws_access_key_id +# MAGIC databricks secrets put-secret brickbyte source-s3/aws_secret_access_key +# MAGIC ``` +# MAGIC +# MAGIC Then just sync - credentials are discovered automatically! -bb = BrickByte() +# COMMAND ---------- + +from brickbyte import Brickbyte + +bb = Brickbyte() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Basic Sync (No Credentials Needed for Faker) +# COMMAND ---------- + +# Simple sync with source-faker (no credentials required) result = bb.sync( source="source-faker", source_config={"count": 100}, @@ -15,3 +46,114 @@ ) print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync with Auto-Discovered Credentials +# MAGIC +# MAGIC If you've set up secrets in scope `brickbyte`, credentials are merged automatically: + +# COMMAND ---------- + +# Credentials auto-discovered from Databricks Secrets +# Just provide non-sensitive config - credentials come from secrets +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "my-bucket", + "region_name": "us-east-1", + "streams": [{"name": "data", "globs": ["**/*.parquet"], "format": {"filetype": "parquet"}}], + }, + # aws_access_key_id and aws_secret_access_key auto-discovered from: + # scope: brickbyte + # keys: source-s3/aws_access_key_id, source-s3/aws_secret_access_key + catalog="", # TODO: Set your catalog + schema="", # TODO: Set your schema +) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## List Configured Sources + +# COMMAND ---------- + +# See which sources have credentials configured +sources = bb.list_configured_sources() +print(f"Sources with credentials: {sources}") + +# Validate specific source +if bb.validate_credentials("source-s3"): + print("βœ“ S3 credentials found") +else: + print("βœ— S3 credentials not configured") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Custom Secrets Scope + +# COMMAND ---------- + +# Use a different secrets scope +bb_custom = BrickByte(secrets_scope="my-team-secrets") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## YAML Profiles (Advanced) +# MAGIC +# MAGIC For credential reuse across multiple sources, use a YAML profiles file: +# MAGIC +# MAGIC ```yaml +# MAGIC # /Workspace/Shared/brickbyte/profiles.yml +# MAGIC profiles: +# MAGIC azure-shared: +# MAGIC tenant_id: "{{ secret('azure/tenant_id') }}" +# MAGIC client_id: "{{ secret('azure/client_id') }}" +# MAGIC client_secret: "{{ secret('azure/client_secret') }}" +# MAGIC +# MAGIC mappings: +# MAGIC source-microsoft-teams: azure-shared +# MAGIC source-azure-blob-storage: azure-shared +# MAGIC ``` + +# COMMAND ---------- + +# Load with YAML profiles +# bb_profiles = BrickByte(profiles="/Workspace/Shared/brickbyte/profiles.yml") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Preview Before Sync + +# COMMAND ---------- + +# Preview before sync (optional) +# preview = bb.preview( +# source="source-faker", +# source_config={"count": 100}, +# catalog="main", +# schema="bronze", +# ) +# print(preview) + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync with AI Metadata Enrichment + +# COMMAND ---------- + +# Sync with AI metadata enrichment +# result = bb.sync( +# source="source-faker", +# source_config={"count": 100}, +# catalog="main", +# schema="bronze", +# enrich_metadata=True, +# enrich_model="databricks-meta-llama-3-3-70b-instruct", +# ) +# print(f"Enriched tables: {result.enriched_tables}") diff --git a/notebooks/brickbyte-gcs.py b/notebooks/brickbyte-gcs.py new file mode 100644 index 0000000..8977a47 --- /dev/null +++ b/notebooks/brickbyte-gcs.py @@ -0,0 +1,252 @@ +# Databricks notebook source +# MAGIC %run ./_setup + +# COMMAND ---------- + +# MAGIC %md +# MAGIC # Google Cloud Storage (GCS) to Databricks with BrickByte +# MAGIC +# MAGIC This notebook syncs files from Google Cloud Storage to Delta Lake tables in Unity Catalog. +# MAGIC +# MAGIC **Supported file formats:** CSV, JSON, Parquet, Avro, JSONL +# MAGIC +# MAGIC **Prerequisites:** +# MAGIC - GCS bucket name +# MAGIC - Service Account with `Storage Object Viewer` role +# MAGIC - Service Account Key (JSON) + +# COMMAND ---------- + +from brickbyte import Brickbyte + +bb = Brickbyte() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Configuration +# MAGIC +# MAGIC 1. Create a Service Account in Google Cloud Console +# MAGIC 2. Grant `Storage Object Viewer` role on your bucket +# MAGIC 3. Generate a JSON key for the service account + +# COMMAND ---------- + +# Your Google Cloud service account credentials (JSON format) +# Store in Databricks Secrets for production: +# service_account_key = dbutils.secrets.get(scope="gcp", key="service_account_json") + +service_account_key = """ +{ + "type": "service_account", + "project_id": "YOUR_PROJECT_ID", + "private_key_id": "YOUR_PRIVATE_KEY_ID", + "private_key": "YOUR_PRIVATE_KEY_PEM_CONTENT", + "client_email": "your-service-account@your-project.iam.gserviceaccount.com", + "client_id": "123456789", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token" +} +""" + +bucket_name = "your-gcs-bucket" + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync CSV Files from GCS + +# COMMAND ---------- + +result = bb.sync( + source="source-gcs", + source_config={ + "gcs_bucket": bucket_name, + "gcs_path": "", # Root of bucket, or specify a prefix like "data/" + "service_account": service_account_key, + "streams": [ + { + "name": "csv_files", + "globs": ["**/*.csv"], + "format": { + "filetype": "csv", + "delimiter": ",", + "header_definition": {"header_definition_type": "Autogenerated"}, + }, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Parquet Files + +# COMMAND ---------- + +result = bb.sync( + source="source-gcs", + source_config={ + "gcs_bucket": bucket_name, + "service_account": service_account_key, + "streams": [ + { + "name": "parquet_data", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync JSON Lines Files + +# COMMAND ---------- + +result = bb.sync( + source="source-gcs", + source_config={ + "gcs_bucket": bucket_name, + "service_account": service_account_key, + "streams": [ + { + "name": "json_events", + "globs": ["events/**/*.jsonl", "logs/**/*.json"], + "format": {"filetype": "jsonl"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Avro Files + +# COMMAND ---------- + +result = bb.sync( + source="source-gcs", + source_config={ + "gcs_bucket": bucket_name, + "service_account": service_account_key, + "streams": [ + { + "name": "avro_data", + "globs": ["**/*.avro"], + "format": {"filetype": "avro"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Multiple Streams from Different Paths + +# COMMAND ---------- + +result = bb.sync( + source="source-gcs", + source_config={ + "gcs_bucket": bucket_name, + "service_account": service_account_key, + "streams": [ + { + "name": "raw_sales", + "globs": ["raw/sales/**/*.csv"], + "format": {"filetype": "csv"}, + }, + { + "name": "processed_analytics", + "globs": ["processed/analytics/**/*.parquet"], + "format": {"filetype": "parquet"}, + }, + { + "name": "event_logs", + "globs": ["logs/**/*.jsonl"], + "format": {"filetype": "jsonl"}, + }, + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from: {result.streams_synced}") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With Start Date Filter (Incremental) + +# COMMAND ---------- + +result = bb.sync( + source="source-gcs", + source_config={ + "gcs_bucket": bucket_name, + "service_account": service_account_key, + "start_date": "2024-01-01T00:00:00Z", # Only files modified after this date + "streams": [ + { + "name": "recent_data", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With AI Metadata Enrichment + +# COMMAND ---------- + +# result = bb.sync( +# source="source-gcs", +# source_config={ +# "gcs_bucket": bucket_name, +# "service_account": service_account_key, +# "streams": [ +# { +# "name": "customer_data", +# "globs": ["customers/**/*.csv"], +# "format": {"filetype": "csv"}, +# } +# ], +# }, +# catalog="", # TODO: Set your Unity Catalog name +# schema="", # TODO: Set your target schema +# enrich_metadata=True, +# enrich_model="databricks-meta-llama-3-3-70b-instruct", +# ) + +# print(f"Enriched tables: {result.enriched_tables}") diff --git a/notebooks/brickbyte-github.ipynb b/notebooks/brickbyte-github.ipynb deleted file mode 100644 index 0519ecb..0000000 --- a/notebooks/brickbyte-github.ipynb +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/notebooks/brickbyte-github.py b/notebooks/brickbyte-github.py index 1597bcf..420d0f8 100644 --- a/notebooks/brickbyte-github.py +++ b/notebooks/brickbyte-github.py @@ -2,7 +2,7 @@ # MAGIC %md # MAGIC # BrickByte - GitHub Example # MAGIC -# MAGIC Sync data from GitHub to Databricks in one line. +# MAGIC Sync data from GitHub to Databricks. # MAGIC # MAGIC ## Prerequisites # MAGIC - GitHub Personal Access Token (https://github.com/settings/tokens) @@ -13,10 +13,13 @@ # COMMAND ---------- -from brickbyte import BrickByte +from brickbyte import Brickbyte -bb = BrickByte() +bb = Brickbyte() +# COMMAND ---------- + +# Simple sync result = bb.sync( source="source-github", source_config={ @@ -29,13 +32,33 @@ }, catalog="", # TODO: Set your Unity Catalog name schema="", # TODO: Set your target schema - # streams=["commits", "issues", "pull_requests"], # Optional: select specific streams + # staging_volume="", # Optional: Set if running outside of Databricks Notebooks + streams=["commits", "issues", "pull_requests"], # Optional: select specific streams ) print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") # COMMAND ---------- +# MAGIC %md +# MAGIC ## Advanced: Preview Before Sync + +# COMMAND ---------- + +# Preview what will change before syncing +# preview = bb.preview( +# source="source-github", +# source_config={ +# "credentials": {"personal_access_token": "ghp_..."}, +# "repositories": ["owner/repo"], +# }, +# catalog="main", +# schema="bronze", +# ) +# print(preview) + +# COMMAND ---------- + # MAGIC %md # MAGIC ## Query Your Data # MAGIC @@ -45,7 +68,7 @@ # MAGIC _airbyte_data:sha AS commit_sha, # MAGIC _airbyte_data:commit.message AS message, # MAGIC _airbyte_data:commit.author.name AS author -# MAGIC FROM your_catalog.your_schema._airbyte_raw_commits +# MAGIC FROM your_catalog.your_schema.commits # MAGIC ORDER BY _airbyte_data:commit.author.date DESC # MAGIC LIMIT 20; # MAGIC @@ -54,7 +77,7 @@ # MAGIC _airbyte_data:number AS issue_number, # MAGIC _airbyte_data:title AS title, # MAGIC _airbyte_data:state AS state -# MAGIC FROM your_catalog.your_schema._airbyte_raw_issues +# MAGIC FROM your_catalog.your_schema.issues # MAGIC WHERE _airbyte_data:state = 'open' # MAGIC LIMIT 20; # MAGIC ``` diff --git a/notebooks/brickbyte-google-drive.py b/notebooks/brickbyte-google-drive.py new file mode 100644 index 0000000..4dd2665 --- /dev/null +++ b/notebooks/brickbyte-google-drive.py @@ -0,0 +1,206 @@ +# Databricks notebook source +# MAGIC %run ./_setup + +# COMMAND ---------- + +# MAGIC %md +# MAGIC # Google Drive to Databricks with BrickByte +# MAGIC +# MAGIC This notebook syncs files from Google Drive to Delta Lake tables in Unity Catalog. +# MAGIC +# MAGIC **Supported file formats:** CSV, JSON, Parquet, Avro, JSONL, Excel, Feather +# MAGIC +# MAGIC **Prerequisites:** +# MAGIC - A Google Cloud service account with access to your Drive folder +# MAGIC - The folder must be shared with your service account email + +# COMMAND ---------- + +from brickbyte import Brickbyte + +bb = Brickbyte() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Configuration +# MAGIC +# MAGIC 1. Get your **Service Account Key** from Google Cloud Console +# MAGIC 2. Get the **Folder Link** from Google Drive (share the folder with your service account) + +# COMMAND ---------- + +# Your Google Cloud service account credentials (JSON format) +# Store this in Databricks Secrets for production use: +# service_account_key = dbutils.secrets.get(scope="google", key="service_account_json") + +service_account_key = """ +{ + "type": "service_account", + "project_id": "YOUR_PROJECT_ID", + "private_key_id": "YOUR_PRIVATE_KEY_ID", + "private_key": "YOUR_PRIVATE_KEY_PEM_CONTENT", + "client_email": "your-service-account@your-project.iam.gserviceaccount.com", + "client_id": "123456789", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token" +} +""" + +# The Google Drive folder link (the folder must be shared with your service account) +folder_link = "https://drive.google.com/drive/folders/YOUR_FOLDER_ID" + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync CSV/JSON Files from Google Drive + +# COMMAND ---------- + +import json + +result = bb.sync( + source="source-google-drive", + source_config={ + "folder_url": folder_link, + "credentials": { + "auth_type": "Service", + "service_account_info": service_account_key, + }, + # File format configuration for CSV files + "streams": [ + { + "name": "my_csv_files", + "globs": ["**/*.csv"], + "format": { + "filetype": "csv", + "delimiter": ",", + "header_definition": {"header_definition_type": "Autogenerated"}, + }, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync JSON Files + +# COMMAND ---------- + +result = bb.sync( + source="source-google-drive", + source_config={ + "folder_url": folder_link, + "credentials": { + "auth_type": "Service", + "service_account_info": service_account_key, + }, + "streams": [ + { + "name": "json_data", + "globs": ["**/*.json"], + "format": {"filetype": "jsonl"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Parquet Files (No Format Config Needed) + +# COMMAND ---------- + +result = bb.sync( + source="source-google-drive", + source_config={ + "folder_url": folder_link, + "credentials": { + "auth_type": "Service", + "service_account_info": service_account_key, + }, + "streams": [ + { + "name": "parquet_data", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync All Supported Files (Wildcard Pattern) + +# COMMAND ---------- + +result = bb.sync( + source="source-google-drive", + source_config={ + "folder_url": folder_link, + "credentials": { + "auth_type": "Service", + "service_account_info": service_account_key, + }, + "streams": [ + { + "name": "all_files", + "globs": ["**/*"], # All files recursively + "format": {"filetype": "unstructured"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With AI Metadata Enrichment + +# COMMAND ---------- + +# Sync with automatic AI-powered metadata generation +# result = bb.sync( +# source="source-google-drive", +# source_config={ +# "folder_url": folder_link, +# "credentials": { +# "auth_type": "Service", +# "service_account_info": service_account_key, +# }, +# "streams": [ +# { +# "name": "csv_with_enrichment", +# "globs": ["**/*.csv"], +# "format": {"filetype": "csv"}, +# } +# ], +# }, +# catalog="", # TODO: Set your Unity Catalog name +# schema="", # TODO: Set your target schema +# enrich_metadata=True, +# enrich_model="databricks-meta-llama-3-3-70b-instruct", +# ) + +# print(f"Enriched tables: {result.enriched_tables}") diff --git a/notebooks/brickbyte-microsoft-teams.py b/notebooks/brickbyte-microsoft-teams.py new file mode 100644 index 0000000..baca7fc --- /dev/null +++ b/notebooks/brickbyte-microsoft-teams.py @@ -0,0 +1,297 @@ +# Databricks notebook source +# MAGIC %run ./_setup + +# COMMAND ---------- + +# MAGIC %md +# MAGIC # Microsoft Teams to Databricks with BrickByte +# MAGIC +# MAGIC This notebook syncs data from Microsoft Teams to Delta Lake tables in Unity Catalog. +# MAGIC +# MAGIC **Available streams:** +# MAGIC - `users` - Organization users +# MAGIC - `groups` - Microsoft 365 groups +# MAGIC - `group_members` - Group membership +# MAGIC - `teams` - Teams in your organization +# MAGIC - `team_members` - Team membership +# MAGIC - `channels` - Channels within teams +# MAGIC - `channel_members` - Channel membership +# MAGIC - `channel_messages` - Messages in channels +# MAGIC - `channel_tabs` - Tabs in channels +# MAGIC - `conversations` - Group conversations +# MAGIC - `conversation_threads` - Conversation threads +# MAGIC - `conversation_posts` - Posts in conversations +# MAGIC - `team_device_usage_report` - Device usage reports +# MAGIC - `team_drives` - Team drives +# MAGIC +# MAGIC **Prerequisites:** +# MAGIC - Azure AD App Registration with Microsoft Graph API permissions +# MAGIC - Client ID, Client Secret, and Tenant ID + +# COMMAND ---------- + +from brickbyte import Brickbyte + +bb = Brickbyte() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Azure AD App Setup +# MAGIC +# MAGIC 1. Go to [Azure Portal](https://portal.azure.com/) β†’ Azure Active Directory β†’ App registrations +# MAGIC 2. Click **New registration**, name it (e.g., "BrickByte Teams Connector") +# MAGIC 3. Under **API permissions**, add Microsoft Graph **Application permissions**: +# MAGIC - `Group.Read.All` +# MAGIC - `Channel.Read.All` +# MAGIC - `Team.ReadBasic.All` +# MAGIC - `User.Read.All` +# MAGIC - `ChannelMessage.Read.All` (for messages) +# MAGIC 4. Click **Grant admin consent** +# MAGIC 5. Under **Certificates & secrets**, create a new client secret +# MAGIC 6. Copy: **Client ID**, **Tenant ID** (from Overview), and **Client Secret** + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 1: Auto-Discovered Credentials (Recommended) +# MAGIC +# MAGIC Set up secrets once: +# MAGIC ```bash +# MAGIC databricks secrets create-scope brickbyte +# MAGIC databricks secrets put-secret brickbyte source-microsoft-teams/tenant_id +# MAGIC databricks secrets put-secret brickbyte source-microsoft-teams/client_id +# MAGIC databricks secrets put-secret brickbyte source-microsoft-teams/client_secret +# MAGIC ``` +# MAGIC +# MAGIC Credentials are auto-discovered and merged into the config! + +# COMMAND ---------- + +# Credentials auto-discovered - just provide non-sensitive config +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + # tenant_id, client_id, client_secret auto-discovered from secrets! + }, + "period": "D7", + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 2: Inline Credentials (Quick Testing) + +# COMMAND ---------- + +# Your Azure AD app credentials (for testing only) +client_id = "YOUR_CLIENT_ID" +client_secret = "YOUR_CLIENT_SECRET" +tenant_id = "YOUR_TENANT_ID" + +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + "client_id": client_id, + "client_secret": client_secret, + "tenant_id": tenant_id, + }, + "period": "D7", # Last 7 days for reports + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") +print(f"Streams: {result.streams_synced}") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Specific Streams Only + +# COMMAND ---------- + +# Sync only teams and channels +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + "client_id": client_id, + "client_secret": client_secret, + "tenant_id": tenant_id, + }, + "period": "D7", + }, + streams=["teams", "channels", "team_members"], + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Users and Groups + +# COMMAND ---------- + +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + "client_id": client_id, + "client_secret": client_secret, + "tenant_id": tenant_id, + }, + "period": "D30", # Last 30 days + }, + streams=["users", "groups", "group_members"], + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Channel Messages +# MAGIC +# MAGIC **Note:** Requires `ChannelMessage.Read.All` permission. + +# COMMAND ---------- + +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + "client_id": client_id, + "client_secret": client_secret, + "tenant_id": tenant_id, + }, + "period": "D7", + }, + streams=["channel_messages"], + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} messages") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Conversations (Group Conversations) + +# COMMAND ---------- + +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + "client_id": client_id, + "client_secret": client_secret, + "tenant_id": tenant_id, + }, + "period": "D7", + }, + streams=["conversations", "conversation_threads", "conversation_posts"], + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Usage Reports + +# COMMAND ---------- + +result = bb.sync( + source="source-microsoft-teams", + source_config={ + "credentials": { + "auth_type": "Client", + "client_id": client_id, + "client_secret": client_secret, + "tenant_id": tenant_id, + }, + "period": "D30", # Reports for last 30 days + }, + streams=["team_device_usage_report"], + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With AI Metadata Enrichment +# MAGIC +# MAGIC Auto-generate column descriptions and detect PII (e.g., email addresses, user names). + +# COMMAND ---------- + +# result = bb.sync( +# source="source-microsoft-teams", +# source_config={ +# "credentials": { +# "auth_type": "Client", +# "client_id": client_id, +# "client_secret": client_secret, +# "tenant_id": tenant_id, +# }, +# "period": "D7", +# }, +# streams=["users", "teams", "channels"], +# catalog="", # TODO: Set your Unity Catalog name +# schema="", # TODO: Set your target schema +# enrich_metadata=True, +# enrich_model="databricks-meta-llama-3-3-70b-instruct", +# ) + +# print(f"Enriched tables: {result.enriched_tables}") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Query Your Teams Data + +# COMMAND ---------- + +# MAGIC %sql +# MAGIC -- View synced teams +# MAGIC -- SELECT * FROM your_catalog.your_schema.teams LIMIT 10 + +# COMMAND ---------- + +# MAGIC %sql +# MAGIC -- View channel messages +# MAGIC -- SELECT * FROM your_catalog.your_schema.channel_messages LIMIT 10 + +# COMMAND ---------- + +# MAGIC %sql +# MAGIC -- Join teams with channels +# MAGIC -- SELECT t.displayName as team_name, c.displayName as channel_name +# MAGIC -- FROM your_catalog.your_schema.teams t +# MAGIC -- JOIN your_catalog.your_schema.channels c ON t.id = c.teamId diff --git a/notebooks/brickbyte-s3.py b/notebooks/brickbyte-s3.py new file mode 100644 index 0000000..58c726d --- /dev/null +++ b/notebooks/brickbyte-s3.py @@ -0,0 +1,364 @@ +# Databricks notebook source +# MAGIC %run ./_setup + +# COMMAND ---------- + +# MAGIC %md +# MAGIC # Amazon S3 to Databricks with BrickByte +# MAGIC +# MAGIC This notebook syncs files from Amazon S3 to Delta Lake tables in Unity Catalog. +# MAGIC +# MAGIC **Supported file formats:** CSV, JSON, Parquet, Avro, JSONL +# MAGIC +# MAGIC **Authentication options:** +# MAGIC - Auto-discovered from Databricks Secrets (recommended) +# MAGIC - IAM Access Key + Secret (inline) +# MAGIC - IAM Role (for Databricks on AWS) +# MAGIC +# MAGIC **Prerequisites:** +# MAGIC - S3 bucket name +# MAGIC - AWS credentials with `s3:GetObject` and `s3:ListBucket` permissions + +# COMMAND ---------- + +from brickbyte import Brickbyte + +bb = Brickbyte() + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 1: Auto-Discovered Credentials (Recommended) +# MAGIC +# MAGIC Set up secrets once with the CLI: +# MAGIC ```bash +# MAGIC databricks secrets create-scope brickbyte +# MAGIC databricks secrets put-secret brickbyte source-s3/aws_access_key_id +# MAGIC databricks secrets put-secret brickbyte source-s3/aws_secret_access_key +# MAGIC databricks secrets put-secret brickbyte source-s3/region_name +# MAGIC ``` +# MAGIC +# MAGIC Then just sync - credentials are discovered automatically! + +# COMMAND ---------- + +# Credentials auto-discovered from Databricks Secrets +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + # aws_access_key_id, aws_secret_access_key, region_name auto-discovered! + "streams": [ + { + "name": "csv_files", + "globs": ["**/*.csv"], + "format": {"filetype": "csv"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 2: Inline Credentials (Quick Testing) + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "streams": [ + { + "name": "csv_files", + "globs": ["**/*.csv"], + "format": {"filetype": "csv"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from {len(result.streams_synced)} streams") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Option 2: Authenticate with IAM Role ARN +# MAGIC +# MAGIC Use this when running on AWS infrastructure with IAM roles. + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "role_arn": "arn:aws:iam::123456789012:role/YourS3AccessRole", + "region_name": "us-east-1", + "streams": [ + { + "name": "data_files", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync from Specific Path Prefix + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "path_prefix": "data/2024/", # Only sync from this prefix + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "streams": [ + { + "name": "2024_data", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync JSON Lines Files + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "streams": [ + { + "name": "event_logs", + "globs": ["logs/**/*.jsonl", "events/**/*.json"], + "format": {"filetype": "jsonl"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Sync Avro Files + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "streams": [ + { + "name": "avro_data", + "globs": ["**/*.avro"], + "format": {"filetype": "avro"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## CSV with Custom Delimiter and Header + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "streams": [ + { + "name": "tsv_files", + "globs": ["**/*.tsv"], + "format": { + "filetype": "csv", + "delimiter": "\t", + "quote_char": '"', + "escape_char": "\\", + "header_definition": {"header_definition_type": "Autogenerated"}, + }, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## Multiple Streams (Different File Types) + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "streams": [ + { + "name": "raw_csv", + "globs": ["raw/**/*.csv"], + "format": {"filetype": "csv"}, + }, + { + "name": "processed_parquet", + "globs": ["processed/**/*.parquet"], + "format": {"filetype": "parquet"}, + }, + { + "name": "logs_jsonl", + "globs": ["logs/**/*.jsonl"], + "format": {"filetype": "jsonl"}, + }, + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records from: {result.streams_synced}") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With Start Date Filter (Incremental) + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-s3-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", + "region_name": "us-east-1", + "start_date": "2024-01-01T00:00:00Z", # Only files modified after this date + "streams": [ + { + "name": "recent_data", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## S3-Compatible Storage (MinIO, DigitalOcean Spaces, etc.) + +# COMMAND ---------- + +result = bb.sync( + source="source-s3", + source_config={ + "bucket": "your-bucket", + "aws_access_key_id": "YOUR_ACCESS_KEY", + "aws_secret_access_key": "YOUR_SECRET_KEY", + "endpoint": "https://nyc3.digitaloceanspaces.com", # Custom endpoint + "streams": [ + { + "name": "data", + "globs": ["**/*.parquet"], + "format": {"filetype": "parquet"}, + } + ], + }, + catalog="", # TODO: Set your Unity Catalog name + schema="", # TODO: Set your target schema +) + +print(f"Synced {result.records_written} records") + +# COMMAND ---------- + +# MAGIC %md +# MAGIC ## With AI Metadata Enrichment + +# COMMAND ---------- + +# result = bb.sync( +# source="source-s3", +# source_config={ +# "bucket": "your-s3-bucket", +# "aws_access_key_id": "YOUR_ACCESS_KEY_ID", +# "aws_secret_access_key": "YOUR_SECRET_ACCESS_KEY", +# "region_name": "us-east-1", +# "streams": [ +# { +# "name": "customer_data", +# "globs": ["customers/**/*.csv"], +# "format": {"filetype": "csv"}, +# } +# ], +# }, +# catalog="", # TODO: Set your Unity Catalog name +# schema="", # TODO: Set your target schema +# enrich_metadata=True, +# enrich_model="databricks-meta-llama-3-3-70b-instruct", +# ) + +# print(f"Enriched tables: {result.enriched_tables}") diff --git a/pyproject.toml b/pyproject.toml index 26919e0..b66b907 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,26 +1,58 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + [project] name = "brickbyte" version = "0.1.0" -description = "Library for PyAirbyte to run on Databricks serverless compute" +description = "Sync data from Airbyte sources to Databricks Unity Catalog with streaming architecture" authors = [ { name="Sri Tikkireddy", email="sri.tikkireddy@databricks.com" }, + { name="Peter Park", email="peter.park@databricks.com" }, ] readme = "README.md" requires-python = ">=3.10" +keywords = ["databricks", "airbyte", "etl", "data-engineering", "unity-catalog", "delta-lake"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Database", + "Topic :: Software Development :: Libraries :: Python Modules", +] dependencies = [ "virtualenv", "databricks-sdk>=0.74.0", - "airbyte>=0.19.0", + "databricks-sql-connector>=4.2.2", + "airbyte>=0.34.0", + "pyarrow>=14.0.0", ] +[project.urls] +Homepage = "https://github.com/park-peter/brickbyte" +Repository = "https://github.com/park-peter/brickbyte" +Issues = "https://github.com/park-peter/brickbyte/issues" + [project.license] -file = "LICENSE.txt" +file = "LICENSE" [project.optional-dependencies] dev = [ "pytest", "ruff==0.6.4", ] +local-spark = [ + "delta-spark>=3.2.0", + "pyspark>=3.5.0", +] + +[tool.setuptools.packages.find] +where = ["src"] [tool.ruff] line-length = 100 diff --git a/src/brickbyte/__init__.py b/src/brickbyte/__init__.py index 2ba1de7..74c5772 100644 --- a/src/brickbyte/__init__.py +++ b/src/brickbyte/__init__.py @@ -1,22 +1,40 @@ """ -BrickByte - Bridge Airbyte's 600+ connectors directly into Databricks. +Brickbyte - Sync data from 600+ sources directly into Databricks. """ +import logging import os import shutil import subprocess -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path from typing import Dict, List, Optional -import virtualenv - from brickbyte.types import Source -# Hardcoded destination install URL - always Databricks -DESTINATION_INSTALL_URL = ( - "git+https://github.com/park-peter/brickbyte.git" - "#subdirectory=integrations/destination-databricks-py" +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) +# Suppress noisy third-party DEBUG/INFO logs +logging.getLogger().setLevel(logging.WARNING) + +_noisy_loggers = [ + "py4j", + "pyspark", + "pyspark.sql.connect", + "pyspark.sql.connect.client", + "databricks", + "databricks.sdk", + "urllib3", + "grpc", + "airbyte", +] +for _logger_name in _noisy_loggers: + logging.getLogger(_logger_name).setLevel(logging.WARNING) + +logger = logging.getLogger("brickbyte") +logger.setLevel(logging.INFO) @dataclass @@ -25,18 +43,21 @@ class SyncResult: records_written: int streams_synced: List[str] + failed_streams: List[str] = field(default_factory=list) + enriched_tables: List[str] = field(default_factory=list) class VirtualEnvManager: - """Manages isolated Python virtual environments for connectors.""" + """Manages isolated Python virtual environments for source connectors.""" def __init__(self, env_dir: str): self.env_dir = env_dir def create_virtualenv(self): + import virtualenv virtualenv.cli_run([self.env_dir]) - def install_airbyte_source( + def install_source( self, source: str, override_install: Optional[str] = None ): library = override_install or f"airbyte-{source}" @@ -46,13 +67,6 @@ def install_airbyte_source( stderr=subprocess.DEVNULL, ) - def install_from_url(self, url: str): - subprocess.check_call( - [os.path.join(self.env_dir, "bin", "pip"), "install", url], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - def delete_virtualenv(self): if os.path.exists(self.env_dir): shutil.rmtree(self.env_dir) @@ -62,45 +76,45 @@ def bin_path(self): return os.path.join(self.env_dir, "bin") -class BrickByte: +class Brickbyte: """ - BrickByte - Sync data from any Airbyte source to Databricks. - - Example (simplest): - bb = BrickByte() - bb.sync( - source="source-faker", - source_config={"count": 100}, - catalog="main", - schema="bronze", - ) - - Example (with stream selection): - bb = BrickByte() - bb.sync( - source="source-github", - source_config={ - "credentials": {"personal_access_token": "..."}, - "repositories": ["owner/repo"], - }, - catalog="main", - schema="bronze", - streams=["commits", "issues"], - ) + Brickbyte - Sync data from any source connector to Databricks. + + Uses a streaming architecture to bypass local disk storage and + write directly to Unity Catalog. + + Supports automatic credential discovery from Databricks Secrets: + - Default scope: "brickbyte" + - Key convention: "{source-name}/{field}" (e.g., "source-s3/aws_access_key_id") + - Optional YAML profiles for credential reuse across sources """ - def __init__(self, base_venv_directory: Optional[str] = None): + def __init__( + self, + base_venv_directory: Optional[str] = None, + secrets_scope: str = "brickbyte", + profiles: Optional[str] = None, + ): """ - Initialize BrickByte. + Initialize Brickbyte. Args: base_venv_directory: Directory to store virtual environments. Defaults to user's home directory. + secrets_scope: Databricks Secrets scope for credential discovery + (default: "brickbyte") + profiles: Optional path to YAML profiles file for advanced + credential configuration (e.g., credential reuse) """ self._base_venv_directory = base_venv_directory or str(Path.home()) self._source_env_managers: Dict[str, VirtualEnvManager] = {} - self._destination_env_manager: Optional[VirtualEnvManager] = None - self._cache = None + + # Initialize credential resolver + from brickbyte.credentials import CredentialResolver + self._credential_resolver = CredentialResolver( + secrets_scope=secrets_scope, + profiles_path=profiles, + ) def _setup_source(self, source: str, source_install: Optional[str] = None): """Install source connector in isolated venv.""" @@ -110,88 +124,88 @@ def _setup_source(self, source: str, source_install: Optional[str] = None): path = os.path.join(self._base_venv_directory, f"brickbyte-{source}") manager = VirtualEnvManager(path) manager.create_virtualenv() - manager.install_airbyte_source(source, source_install) + manager.install_source(source, source_install) self._source_env_managers[source] = manager - def _setup_destination(self): - """Install Databricks destination connector in isolated venv.""" - if self._destination_env_manager: - return - - path = os.path.join( - self._base_venv_directory, "brickbyte-destination-databricks" - ) - manager = VirtualEnvManager(path) - manager.create_virtualenv() - manager.install_from_url(DESTINATION_INSTALL_URL) - self._destination_env_manager = manager - def _get_source_exec_path(self, source: str) -> str: """Get path to source connector executable.""" return os.path.join(self._source_env_managers[source].bin_path, source) - def _get_destination_exec_path(self) -> str: - """Get path to destination connector executable.""" - return os.path.join( - self._destination_env_manager.bin_path, "destination-databricks" - ) - - def _get_cache(self): - """Get or create local cache for state management.""" - if self._cache: - return self._cache - - import airbyte as ab - - path = os.path.join(self._base_venv_directory, "brickbyte", "cache") - self._cache = ab.new_local_cache( - cache_name="brickbyte", cache_dir=path, cleanup=True - ) - return self._cache + def _validate_sync_params( + self, + mode: str, + staging_volume: str, + ): + """Validate sync parameters.""" + valid_modes = ("append", "overwrite") + if mode not in valid_modes: + if mode == "merge": + raise NotImplementedError("Merge mode is not yet supported.") + raise ValueError( + f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)}" + ) - def _get_destination( + def preview( self, + source: str, + source_config: dict, catalog: str, schema: str, - warehouse_id: Optional[str] = None, + streams: Optional[List[str]] = None, + source_install: Optional[str] = None, + sample_size: int = 5, ): - """Get configured Databricks destination.""" + """ + Preview a sync operation. + + Args: + source: Source connector name + source_config: Configuration dictionary for the source + catalog: Unity Catalog name + schema: Target schema name + streams: List of streams to preview (None = all streams) + source_install: Override source installation + sample_size: Number of sample records per stream + + Returns: + PreviewResult with detailed comparison + """ import airbyte as ab - from databricks.sdk import WorkspaceClient - - w = WorkspaceClient() - server_hostname = w.config.host.replace("https://", "").rstrip("/") - token = w.config.token - - # Auto-discover warehouse if not provided - if not warehouse_id: - warehouses = list(w.warehouses.list()) - running = [ - wh - for wh in warehouses - if wh.state and wh.state.value == "RUNNING" - ] - if running: - warehouse_id = running[0].id + + from brickbyte.preview import PreviewEngine + + merged_config = self._credential_resolver.merge_credentials(source, source_config) + + try: + logger.info(f"Setting up {source}...") + self._setup_source(source, source_install) + + ab_source = ab.get_source( + source, + config=merged_config, + local_executable=self._get_source_exec_path(source), + ) + ab_source.check() + + if streams: + ab_source.select_streams(streams) else: - raise ValueError( - "No running SQL warehouse found. " - "Specify warehouse_id or start a warehouse." - ) - - http_path = f"/sql/1.0/warehouses/{warehouse_id}" - - return ab.get_destination( - "destination-databricks", - config={ - "server_hostname": server_hostname, - "http_path": http_path, - "token": token, - "catalog": catalog, - "schema": schema, - }, - local_executable=self._get_destination_exec_path(), - ) + ab_source.select_all_streams() + + selected = list(ab_source.get_selected_streams()) + + logger.info("Generating preview (streaming)...") + engine = PreviewEngine(catalog=catalog, schema=schema) + result = engine.preview( + ab_source=ab_source, + streams=selected, + sample_size=sample_size, + ) + + return result + + finally: + self.cleanup() def sync( self, @@ -199,65 +213,63 @@ def sync( source_config: dict, catalog: str, schema: str, + staging_volume: Optional[str] = None, streams: Optional[List[str]] = None, + mode: str = "overwrite", + flatten: bool = False, + enrich_metadata: bool = False, + enrich_model: Optional[str] = None, warehouse_id: Optional[str] = None, - mode: str = "full_refresh", source_install: Optional[str] = None, cleanup: bool = True, + buffer_size_records: int = 50000, + buffer_size_mb: int = 100, + continue_on_error: bool = False, ) -> SyncResult: """ - Sync data from an Airbyte source to Databricks. - - This is the main method - it handles everything: - - Installs source connector in isolated venv - - Installs Databricks destination connector - - Configures and validates source connection - - Auto-discovers warehouse and authenticates to Databricks - - Syncs data - - Cleans up virtual environments - + Sync data from a source connector to Databricks (Streaming). + Args: - source: Airbyte source connector name (e.g., "source-github") + source: Source connector name (e.g., "source-github") source_config: Configuration dictionary for the source connector catalog: Unity Catalog name (e.g., "main") schema: Target schema name (e.g., "bronze") + staging_volume: Unity Catalog Volume path (REQUIRED for remote) streams: List of streams to sync. None = all streams (default) - warehouse_id: SQL warehouse ID. Auto-discovered if not provided. - mode: "full_refresh" (default) or "incremental" + mode: Write mode ("overwrite" or "append") + flatten: If True, flatten record fields into columns. + If False (default), store as JSON in 'data' column. + enrich_metadata: If True, use AI to generate column descriptions + enrich_model: Foundation Model endpoint for enrichment + warehouse_id: SQL warehouse ID (optional, auto-discovered) source_install: Override source installation (e.g., custom git URL) cleanup: Whether to cleanup venvs after sync (default: True) + buffer_size_records: Records per micro-batch (default: 50k) + buffer_size_mb: Max batch size in MB (default: 100MB) + continue_on_error: If True, continue syncing other streams if one fails Returns: - SyncResult with records_written and streams_synced - - Example: - bb = BrickByte() - result = bb.sync( - source="source-faker", - source_config={"count": 100}, - catalog="main", - schema="bronze", - ) - print(f"Synced {result.records_written} records") + SyncResult with records_written, streams_synced, failed_streams, enriched_tables """ import airbyte as ab + from brickbyte.writers import create_streaming_writer + + self._validate_sync_params(mode, staging_volume) + merged_config = self._credential_resolver.merge_credentials(source, source_config) + try: - # Setup connectors - print(f"Setting up {source}...") + logger.info(f"Setting up {source}...") self._setup_source(source, source_install) - print("Setting up Databricks destination...") - self._setup_destination() - # Configure source - print(f"Configuring {source}...") + logger.info(f"Configuring {source}...") ab_source = ab.get_source( source, - config=source_config, + config=merged_config, local_executable=self._get_source_exec_path(source), ) - print("Validating source connection...") + logger.info("Validating source connection...") ab_source.check() if streams: @@ -267,26 +279,87 @@ def sync( selected = list(ab_source.get_selected_streams()) - # Configure destination - print("Configuring Databricks destination...") - destination = self._get_destination(catalog, schema, warehouse_id) - - # Get cache - cache = self._get_cache() - - # Sync - force_full_refresh = mode == "full_refresh" - print(f"Syncing {len(selected)} streams to {catalog}.{schema}…") - result = destination.write( - ab_source, cache=cache, force_full_refresh=force_full_refresh + via_msg = f" via {staging_volume}" if staging_volume else " (Native Spark)" + logger.info(f"Streaming {len(selected)} streams to {catalog}.{schema}{via_msg}...") + + writer = create_streaming_writer( + catalog=catalog, + schema=schema, + staging_volume=staging_volume, + warehouse_id=warehouse_id, + buffer_size_records=buffer_size_records, + buffer_size_mb=buffer_size_mb, + flatten=flatten, ) - - records = getattr(result, "processed_records", 0) - print(f"βœ“ Synced {records} records from {len(selected)} streams") + + total_records = 0 + failed_streams: List[str] = [] + + for stream_name in selected: + logger.info(f" Streaming: {stream_name}") + + if mode == "overwrite": + writer.drop_table(stream_name) + + try: + records_generator = ab_source.get_records(stream_name) + count = 0 + for record in records_generator: + writer.write_record(stream_name, record) + count += 1 + if count % 10000 == 0: + logger.info(f" ...streamed {count} records") + + writer.flush_stream(stream_name) + logger.info(f" βœ“ {count} records streamed") + total_records += count + except Exception as e: + error_name = type(e).__name__ + logger.error(f" βœ— Failed to stream {stream_name}: {e}") + failed_streams.append(stream_name) + + is_fatal = "ConnectorFailed" in error_name + if is_fatal and not continue_on_error: + raise + if not continue_on_error: + raise + + if failed_streams: + if continue_on_error: + logger.warning( + f"Completed with {len(failed_streams)} failed streams: {failed_streams}" + ) + else: + raise RuntimeError(f"Sync failed. Failed streams: {failed_streams}") + + writer.close() + + successful_streams = [s for s in selected if s not in failed_streams] + + enriched_tables = [] + if enrich_metadata and successful_streams: + logger.info("Enriching metadata with AI...") + from brickbyte.enrichment import enrich_table + + model = enrich_model or "databricks-meta-llama-3-3-70b-instruct" + for stream_name in successful_streams: + try: + enrich_table( + catalog=catalog, + schema=schema, + table=stream_name, + apply_to_catalog=True, + model_name=model, + ) + enriched_tables.append(stream_name) + except Exception as e: + logger.warning(f" Warning: Could not enrich {stream_name}: {e}") return SyncResult( - records_written=records, - streams_synced=selected, + records_written=total_records, + streams_synced=successful_streams, + failed_streams=failed_streams, + enriched_tables=enriched_tables, ) finally: @@ -294,14 +367,18 @@ def sync( self.cleanup() def cleanup(self): - """Remove virtual environments created by BrickByte.""" + """Remove virtual environments.""" for manager in self._source_env_managers.values(): manager.delete_virtualenv() self._source_env_managers.clear() - if self._destination_env_manager: - self._destination_env_manager.delete_virtualenv() - self._destination_env_manager = None + def list_configured_sources(self) -> List[str]: + """List all sources that have credentials configured.""" + return self._credential_resolver.list_available_sources() + + def validate_credentials(self, source: str) -> bool: + """Check if credentials are configured for a source.""" + return self._credential_resolver.validate(source) -__all__ = ["BrickByte", "SyncResult", "Source"] +__all__ = ["Brickbyte", "SyncResult", "Source"] diff --git a/src/brickbyte/credentials.py b/src/brickbyte/credentials.py new file mode 100644 index 0000000..b7b5369 --- /dev/null +++ b/src/brickbyte/credentials.py @@ -0,0 +1,302 @@ +""" +Credential management for BrickByte. + +Provides automatic credential resolution from Databricks Secrets +with optional YAML profiles for advanced use cases. +""" +import logging +import re +from typing import Any, Dict, List, Optional + +logger = logging.getLogger("brickbyte.credentials") + + +class CredentialResolver: + """ + Resolves credentials from Databricks Secrets with convention-based discovery. + + Default convention: + Scope: "brickbyte" (configurable) + Keys: "{source-name}/{field}" (e.g., "source-s3/aws_access_key_id") + + Usage: + resolver = CredentialResolver() + creds = resolver.get_credentials("source-s3") + # Returns: {"aws_access_key_id": "...", "aws_secret_access_key": "..."} + """ + + def __init__( + self, + secrets_scope: str = "brickbyte", + profiles_path: Optional[str] = None, + ): + """ + Initialize the credential resolver. + + Args: + secrets_scope: Databricks Secrets scope name (default: "brickbyte") + profiles_path: Optional path to YAML profiles file for advanced config + """ + self.secrets_scope = secrets_scope + self.profiles_path = profiles_path + self._cache: Dict[str, Dict[str, Any]] = {} + self._profiles: Optional[Dict[str, Any]] = None + self._mappings: Dict[str, str] = {} + self._dbutils = None + self._available_keys: Optional[List[str]] = None + + # Load profiles if provided + if profiles_path: + self._load_profiles(profiles_path) + + def _get_dbutils(self): + """Get dbutils instance (lazy loading).""" + if self._dbutils is None: + try: + from pyspark.sql import SparkSession + spark = SparkSession.getActiveSession() + if spark: + from pyspark.dbutils import DBUtils + self._dbutils = DBUtils(spark) + except Exception: + pass + + if self._dbutils is None: + try: + import IPython + self._dbutils = IPython.get_ipython().user_ns.get("dbutils") + except Exception: + pass + + return self._dbutils + + def _list_secrets_for_source(self, source: str) -> List[str]: + """List all secret keys available for a source.""" + dbutils = self._get_dbutils() + if not dbutils: + return [] + + try: + # Cache the full list of keys + if self._available_keys is None: + secrets = dbutils.secrets.list(self.secrets_scope) + self._available_keys = [s.key for s in secrets] + + # Filter keys that start with the source name + prefix = f"{source}/" + return [ + key[len(prefix):] for key in self._available_keys + if key.startswith(prefix) + ] + except Exception as e: + logger.debug(f"Could not list secrets: {e}") + return [] + + def _get_secret(self, key: str) -> Optional[str]: + """Get a single secret value.""" + dbutils = self._get_dbutils() + if not dbutils: + return None + + try: + return dbutils.secrets.get(scope=self.secrets_scope, key=key) + except Exception as e: + logger.debug(f"Could not get secret {key}: {e}") + return None + + def _load_profiles(self, path: str): + """Load YAML profiles from file.""" + try: + import yaml + + # Handle workspace paths + if path.startswith("/Workspace"): + dbutils = self._get_dbutils() + if dbutils: + content = dbutils.fs.head(f"file:{path}", 65536) + else: + with open(path, "r") as f: + content = f.read() + else: + with open(path, "r") as f: + content = f.read() + + data = yaml.safe_load(content) + self._profiles = data.get("profiles", {}) + self._mappings = data.get("mappings", {}) + logger.info(f"Loaded {len(self._profiles)} profiles from {path}") + except Exception as e: + logger.warning(f"Could not load profiles from {path}: {e}") + self._profiles = {} + self._mappings = {} + + def _resolve_profile(self, profile_name: str) -> Dict[str, Any]: + """Resolve a named profile to credentials.""" + if not self._profiles or profile_name not in self._profiles: + return {} + + profile = self._profiles[profile_name] + resolved = {} + + for key, value in profile.items(): + if isinstance(value, str): + # Check for secret reference: {{ secret('scope/key') }} or {{ secret('key') }} + match = re.match(r"\{\{\s*secret\(['\"]([^'\"]+)['\"]\)\s*\}\}", value) + if match: + secret_ref = match.group(1) + # If no scope specified, use the default scope + if "/" in secret_ref: + scope_key = secret_ref + else: + scope_key = secret_ref + + secret_value = self._get_secret(scope_key) + if secret_value: + resolved[key] = secret_value + else: + resolved[key] = value + else: + resolved[key] = value + + return resolved + + def get_credentials(self, source: str) -> Dict[str, Any]: + """ + Get credentials for a source. + + Resolution order: + 1. Check if source is mapped to a profile (from YAML) + 2. Fall back to convention-based discovery from secrets + + Args: + source: Source connector name (e.g., "source-s3") + + Returns: + Dictionary of credentials for the source + """ + # Return cached if available + if source in self._cache: + return self._cache[source] + + credentials = {} + + # Check for profile mapping first + if source in self._mappings: + profile_name = self._mappings[source] + credentials = self._resolve_profile(profile_name) + if credentials: + logger.debug(f"Resolved credentials for {source} from profile '{profile_name}'") + + # Fall back to convention-based discovery + if not credentials: + keys = self._list_secrets_for_source(source) + for key in keys: + full_key = f"{source}/{key}" + value = self._get_secret(full_key) + if value: + credentials[key] = value + + if credentials: + logger.debug(f"Discovered {len(credentials)} credentials for {source} from secrets") + + # Cache the result + self._cache[source] = credentials + return credentials + + def merge_credentials( + self, + source: str, + source_config: Dict[str, Any], + ) -> Dict[str, Any]: + """ + Merge discovered credentials into source_config. + + Explicit values in source_config take precedence over discovered credentials. + + Args: + source: Source connector name + source_config: User-provided configuration + + Returns: + Merged configuration with credentials + """ + discovered = self.get_credentials(source) + if not discovered: + return source_config + + # Deep merge - discovered credentials as base, source_config overrides + merged = self._deep_merge(discovered, source_config) + return merged + + def _deep_merge(self, base: Dict, override: Dict) -> Dict: + """Deep merge two dictionaries, with override taking precedence.""" + result = base.copy() + for key, value in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = self._deep_merge(result[key], value) + else: + result[key] = value + return result + + def validate(self, source: str) -> bool: + """ + Validate that credentials exist for a source. + + Args: + source: Source connector name + + Returns: + True if credentials were found + """ + creds = self.get_credentials(source) + return len(creds) > 0 + + def list_available_sources(self) -> List[str]: + """List all sources that have credentials configured.""" + dbutils = self._get_dbutils() + if not dbutils: + return list(self._mappings.keys()) + + try: + if self._available_keys is None: + secrets = dbutils.secrets.list(self.secrets_scope) + self._available_keys = [s.key for s in secrets] + + # Extract unique source names from keys + sources = set() + for key in self._available_keys: + if "/" in key: + source = key.split("/")[0] + sources.add(source) + + # Add mapped sources + sources.update(self._mappings.keys()) + + return sorted(sources) + except Exception: + return list(self._mappings.keys()) + + def clear_cache(self): + """Clear the credential cache.""" + self._cache.clear() + self._available_keys = None + + +def create_credential_resolver( + secrets_scope: str = "brickbyte", + profiles_path: Optional[str] = None, +) -> CredentialResolver: + """ + Create a credential resolver. + + Args: + secrets_scope: Databricks Secrets scope (default: "brickbyte") + profiles_path: Optional path to YAML profiles file + + Returns: + CredentialResolver instance + """ + return CredentialResolver( + secrets_scope=secrets_scope, + profiles_path=profiles_path, + ) diff --git a/src/brickbyte/enrichment/__init__.py b/src/brickbyte/enrichment/__init__.py new file mode 100644 index 0000000..2200174 --- /dev/null +++ b/src/brickbyte/enrichment/__init__.py @@ -0,0 +1,12 @@ +""" +BrickByte Enrichment Module. + +Provides AI-powered metadata enrichment for tables: +- Column descriptions via Foundation Models +- PII detection +- Data classification +""" +from brickbyte.enrichment.semantic import SemanticEnricher, enrich_table + +__all__ = ["SemanticEnricher", "enrich_table"] + diff --git a/src/brickbyte/enrichment/semantic.py b/src/brickbyte/enrichment/semantic.py new file mode 100644 index 0000000..0b96fc6 --- /dev/null +++ b/src/brickbyte/enrichment/semantic.py @@ -0,0 +1,351 @@ +""" +AI-powered semantic enrichment for BrickByte. +Uses Databricks Foundation Models to generate metadata. +""" +import json +import logging +import re +from dataclasses import dataclass, field +from typing import Dict, List, Optional + +logger = logging.getLogger(__name__) + + +@dataclass +class ColumnEnrichment: + """Enrichment results for a single column.""" + + column_name: str + description: Optional[str] = None + is_pii: bool = False + pii_type: Optional[str] = None # e.g., "email", "phone", "ssn", "name" + data_classification: Optional[str] = None # e.g., "public", "internal", "confidential" + + def __str__(self) -> str: + parts = [f"{self.column_name}:"] + if self.description: + parts.append(f' "{self.description}"') + if self.is_pii: + parts.append(f" ⚠️ PII detected: {self.pii_type}") + if self.data_classification: + parts.append(f" Classification: {self.data_classification}") + return "\n".join(parts) + + +@dataclass +class TableEnrichment: + """Enrichment results for a table.""" + + table_name: str + columns: List[ColumnEnrichment] = field(default_factory=list) + table_description: Optional[str] = None + + def __str__(self) -> str: + lines = [f"Table: {self.table_name}"] + if self.table_description: + lines.append(f"Description: {self.table_description}") + lines.append("") + for col in self.columns: + lines.append(str(col)) + return "\n".join(lines) + + +# Prompt template for Foundation Model +ENRICHMENT_PROMPT = """Analyze this database table and provide metadata enrichment. + +Table: {table_name} +Columns and sample data: +{column_samples} + +For each column, provide: +1. A brief description (1-2 sentences) +2. Whether it contains PII (personally identifiable information) +3. If PII, what type (email, phone, ssn, name, address, etc.) +4. Data classification (public, internal, confidential, restricted) + +Also provide a brief description of the table's purpose. + +Respond in JSON format: +{{ + "table_description": "Brief description of the table", + "columns": [ + {{ + "name": "column_name", + "description": "Description of the column", + "is_pii": true/false, + "pii_type": "type or null", + "classification": "public/internal/confidential/restricted" + }} + ] +}} +""" + + +class SemanticEnricher: + """ + AI-powered semantic enrichment using Databricks Foundation Models. + + Generates: + - Column descriptions from data samples + - PII detection + - Data classification suggestions + """ + + def __init__( + self, + model_name: str = "databricks-meta-llama-3-3-70b-instruct", + sample_rows: int = 50, + ): + """ + Initialize the enricher. + + Args: + model_name: Foundation Model endpoint name + sample_rows: Number of sample rows to analyze + """ + self.model_name = model_name + self.sample_rows = sample_rows + self._spark = None + self._client = None + + @property + def spark(self): + """Get or create Spark session.""" + if self._spark is None: + from pyspark.sql import SparkSession + self._spark = SparkSession.builder.getOrCreate() + return self._spark + + @property + def client(self): + """Get or create Databricks SDK client.""" + if self._client is None: + from databricks.sdk import WorkspaceClient + self._client = WorkspaceClient() + return self._client + + def _get_column_samples(self, table_name: str) -> Dict[str, List[str]]: + """Get sample values for each column by parsing data JSON column.""" + # Try new column name first, fall back to legacy name + schema = self.spark.table(table_name).schema + col_names = [f.name for f in schema.fields] + + if "data" in col_names: + data_col = "data" + elif "_airbyte_data" in col_names: + data_col = "_airbyte_data" + else: + # Flattened mode - sample all columns directly + df = self.spark.sql( + f"SELECT * FROM {table_name} LIMIT {self.sample_rows}" + ).toPandas() + samples = {} + for col in df.columns: + if not col.startswith("_"): + vals = df[col].dropna().astype(str).head(5).tolist() + samples[col] = [v[:100] for v in vals] + return samples + + df = self.spark.sql( + f"SELECT {data_col} FROM {table_name} LIMIT {self.sample_rows}" + ).toPandas() + + samples = {} + for _, row in df.iterrows(): + try: + record = json.loads(row[data_col]) + for col, value in record.items(): + if col not in samples: + samples[col] = [] + if value is not None and len(samples[col]) < 5: + samples[col].append(str(value)[:100]) + except (json.JSONDecodeError, KeyError, TypeError): + continue + + return samples + + def _format_samples_for_prompt(self, samples: Dict[str, List[str]]) -> str: + """Format column samples for the prompt.""" + lines = [] + for col, values in samples.items(): + values_str = ", ".join(f'"{v}"' for v in values[:3]) + lines.append(f"- {col}: {values_str}") + return "\n".join(lines) + + def _call_foundation_model(self, prompt: str) -> str: + """Call the Foundation Model API.""" + try: + from databricks.sdk.service.serving import ( + ChatMessage, + ChatMessageRole, + ) + + response = self.client.serving_endpoints.query( + name=self.model_name, + messages=[ChatMessage(role=ChatMessageRole.USER, content=prompt)], + ) + return response.choices[0].message.content + except Exception as e: + logger.warning(f"Warning: Foundation Model call failed: {e}") + return "{}" + + def _parse_enrichment_response( + self, + response: str, + table_name: str, + ) -> TableEnrichment: + """Parse the Foundation Model response into structured enrichment.""" + enrichment = TableEnrichment(table_name=table_name) + + # Try to extract JSON from response + try: + # Find JSON in response (may have surrounding text) + json_match = re.search(r'\{[\s\S]*\}', response) + if json_match: + data = json.loads(json_match.group()) + else: + data = {} + except json.JSONDecodeError: + data = {} + + enrichment.table_description = data.get("table_description") + + for col_data in data.get("columns", []): + col = ColumnEnrichment( + column_name=col_data.get("name", ""), + description=col_data.get("description"), + is_pii=col_data.get("is_pii", False), + pii_type=col_data.get("pii_type"), + data_classification=col_data.get("classification"), + ) + if col.column_name: + enrichment.columns.append(col) + + return enrichment + + def enrich(self, table_name: str) -> TableEnrichment: + """ + Generate semantic enrichment for a table. + + Args: + table_name: Fully qualified table name (catalog.schema.table) + + Returns: + TableEnrichment with AI-generated metadata + """ + logger.info(f" Analyzing table: {table_name}") + + # Get column samples + samples = self._get_column_samples(table_name) + + if not samples: + logger.info(" No data columns found to analyze") + return TableEnrichment(table_name=table_name) + + # Build prompt + samples_str = self._format_samples_for_prompt(samples) + prompt = ENRICHMENT_PROMPT.format( + table_name=table_name, + column_samples=samples_str, + ) + + # Call Foundation Model + logger.info(" Calling Foundation Model...") + response = self._call_foundation_model(prompt) + + # Parse response + enrichment = self._parse_enrichment_response(response, table_name) + + logger.info(f" βœ“ Generated descriptions for {len(enrichment.columns)} columns") + + return enrichment + + def apply_to_catalog(self, enrichment: TableEnrichment): + """ + Apply enrichment metadata to Unity Catalog. + + Since data may be stored as JSON in data column, we store field-level + metadata as table tags and set the table description. + """ + logger.info(f" Applying metadata to {enrichment.table_name}") + + # Set table comment + if enrichment.table_description: + try: + escaped_desc = enrichment.table_description.replace("'", "''") + self.spark.sql( + f"COMMENT ON TABLE {enrichment.table_name} IS '{escaped_desc}'" + ) + logger.info(" βœ“ Set table description") + except Exception as e: + logger.warning(f" Warning: Could not set table comment: {e}") + + # Store field metadata as table tags (fields are inside JSON) + pii_fields = [] + for col in enrichment.columns: + if col.is_pii: + pii_fields.append(f"{col.column_name}:{col.pii_type or 'pii'}") + + if pii_fields: + try: + pii_value = ",".join(pii_fields) + self.spark.sql( + f"ALTER TABLE {enrichment.table_name} " + f"SET TAGS ('pii_fields' = '{pii_value}')" + ) + logger.info(f" βœ“ Tagged PII fields: {pii_fields}") + except Exception as e: + logger.warning(f" Warning: Could not set PII tags: {e}") + + # Store column descriptions as table property for reference + if enrichment.columns: + try: + # Build a summary of field descriptions + desc_summary = "; ".join( + f"{c.column_name}: {c.description}" + for c in enrichment.columns[:10] # Limit to avoid huge properties + if c.description + ) + if desc_summary: + escaped = desc_summary.replace("'", "''")[:1000] + self.spark.sql( + f"ALTER TABLE {enrichment.table_name} " + f"SET TBLPROPERTIES ('brickbyte.field_descriptions' = '{escaped}')" + ) + logger.info(" βœ“ Stored field descriptions in table properties") + except Exception as e: + logger.warning(f" Warning: Could not set field descriptions: {e}") + + logger.info(" βœ“ Applied metadata to catalog") + + +def enrich_table( + catalog: str, + schema: str, + table: str, + apply_to_catalog: bool = True, + model_name: str = "databricks-meta-llama-3-3-70b-instruct", +) -> TableEnrichment: + """ + Convenience function to enrich a single table. + + Args: + catalog: Unity Catalog name + schema: Schema name + table: Table name + apply_to_catalog: Whether to apply metadata to Unity Catalog + model_name: Foundation Model to use + + Returns: + TableEnrichment with AI-generated metadata + """ + table_name = f"{catalog}.{schema}.{table}" + + enricher = SemanticEnricher(model_name=model_name) + enrichment = enricher.enrich(table_name) + + if apply_to_catalog: + enricher.apply_to_catalog(enrichment) + + return enrichment + diff --git a/src/brickbyte/preview.py b/src/brickbyte/preview.py new file mode 100644 index 0000000..5c7725e --- /dev/null +++ b/src/brickbyte/preview.py @@ -0,0 +1,275 @@ +""" +Preview engine for BrickByte. +Provides diff calculation and schema comparison before syncing. +""" +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +@dataclass +class SchemaChange: + """Represents a schema change between source and target.""" + + column: str + change_type: str # "added", "removed", "type_changed" + source_type: Optional[str] = None + target_type: Optional[str] = None + + def __str__(self) -> str: + if self.change_type == "added": + return f" + {self.column} ({self.source_type}) - NEW" + elif self.change_type == "removed": + return f" - {self.column} ({self.target_type}) - REMOVED" + else: + return f" ~ {self.column}: {self.target_type} -> {self.source_type}" + + +@dataclass +class StreamPreview: + """Preview information for a single stream.""" + + stream_name: str + source_count: int + target_count: int + new_records: int = 0 + modified_records: int = 0 + deleted_records: int = 0 + schema_changes: List[SchemaChange] = field(default_factory=list) + sample_records: List[dict] = field(default_factory=list) + + def __str__(self) -> str: + parts = [] + + # Record counts + if self.new_records > 0: + parts.append(f"+{self.new_records} new") + if self.modified_records > 0: + parts.append(f"~{self.modified_records} modified") + if self.deleted_records > 0: + parts.append(f"-{self.deleted_records} deleted") + + if not parts: + if self.source_count >= 0: + parts.append(f"{self.source_count} records") + else: + parts.append("Unknown records (Streaming)") + + line = f"{self.stream_name}: {' | '.join(parts)}" + + # Schema changes + if self.schema_changes: + line += "\n Schema changes:" + for change in self.schema_changes: + line += f"\n {change}" + + return line + + +@dataclass +class PreviewResult: + """Complete preview result for all streams.""" + + streams: List[StreamPreview] = field(default_factory=list) + total_source_records: int = 0 + total_new_records: int = 0 + total_modified_records: int = 0 + total_deleted_records: int = 0 + has_schema_changes: bool = False + + def __str__(self) -> str: + lines = ["=" * 60, "Sync Preview", "=" * 60, ""] + + for stream in self.streams: + lines.append(str(stream)) + + lines.append("") + lines.append("-" * 60) + lines.append( + f"Total: {self.total_source_records} records " + f"(+{self.total_new_records} new, " + f"~{self.total_modified_records} modified, " + f"-{self.total_deleted_records} deleted)" + ) + + if self.has_schema_changes: + lines.append("⚠️ Schema changes detected") + + lines.append("=" * 60) + + return "\n".join(lines) + + +class PreviewEngine: + """ + Generates previews of sync operations. + + Compares source data (sampled) with existing target tables to show: + - Target record counts + - Schema changes (inferred from samples) + - Sample records + """ + + def __init__(self, catalog: str, schema: str): + """ + Initialize the preview engine. + + Args: + catalog: Unity Catalog name + schema: Target schema name + """ + self.catalog = catalog + self.schema = schema + self._spark = None + + @property + def spark(self): + """Get or create Spark session.""" + if self._spark is None: + try: + from pyspark.sql import SparkSession + self._spark = SparkSession.builder.getOrCreate() + except ImportError: + return None + return self._spark + + def get_table_name(self, stream_name: str) -> str: + """Get fully qualified table name.""" + return f"{self.catalog}.{self.schema}.{stream_name}" + + def table_exists(self, stream_name: str) -> bool: + """Check if target table exists.""" + if not self.spark: + return False + + table_name = self.get_table_name(stream_name) + return self.spark.catalog.tableExists(table_name) + + def get_target_count(self, stream_name: str) -> int: + """Get record count from target table.""" + if not self.table_exists(stream_name): + return 0 + + table_name = self.get_table_name(stream_name) + return self.spark.table(table_name).count() + + def get_target_schema(self, stream_name: str) -> Dict[str, str]: + """Get schema of target table.""" + if not self.table_exists(stream_name): + return {} + + table_name = self.get_table_name(stream_name) + df = self.spark.table(table_name) + return {f.name: str(f.dataType) for f in df.schema.fields} + + def get_source_schema(self, sample_records: List[dict]) -> Dict[str, str]: + """Infer schema from sample records.""" + if not sample_records: + return {} + + # Simple inference from first record + # In a real scenario, we might want to check Airbyte catalog + record = sample_records[0] + return {k: type(v).__name__ for k, v in record.items()} + + def compare_schemas( + self, + source_schema: Dict[str, str], + target_schema: Dict[str, str], + ) -> List[SchemaChange]: + """Compare source and target schemas.""" + changes = [] + + source_cols = set(source_schema.keys()) + target_cols = set(target_schema.keys()) + + # New columns + for col in source_cols - target_cols: + changes.append(SchemaChange( + column=col, + change_type="added", + source_type=source_schema[col], + )) + + # Removed columns + for col in target_cols - source_cols: + changes.append(SchemaChange( + column=col, + change_type="removed", + target_type=target_schema[col], + )) + + # Type changes - simplified + # Note: inferred source types (python types) vs target types (spark types) + # mismatch is expected, so we largely skip strict type comparison here + # unless we map them properly. For now, we omit type_changed to avoid noise. + + return changes + + def preview_stream( + self, + ab_source: Any, + stream_name: str, + sample_size: int = 5, + ) -> StreamPreview: + """Generate preview for a single stream.""" + target_count = self.get_target_count(stream_name) + + # Get samples from stream + sample_records = [] + try: + # We only peek at the first N records + records_gen = ab_source.get_records(stream_name) + for i, record in enumerate(records_gen): + if i >= sample_size: + break + sample_records.append(record) + except Exception: + pass + + # Compare schemas + source_schema = self.get_source_schema(sample_records) + target_schema = self.get_target_schema(stream_name) + schema_changes = self.compare_schemas(source_schema, target_schema) + + return StreamPreview( + stream_name=stream_name, + source_count=-1, # Unknown in streaming + target_count=target_count, + new_records=-1, # Unknown + modified_records=-1, # Unknown + deleted_records=-1, # Unknown + schema_changes=schema_changes, + sample_records=sample_records, + ) + + def preview( + self, + ab_source: Any, + streams: List[str], + sample_size: int = 5, + ) -> PreviewResult: + """ + Generate preview for all streams. + + Args: + ab_source: Initialized Airbyte source + streams: List of stream names + sample_size: Number of sample records per stream + + Returns: + PreviewResult with all stream previews + """ + result = PreviewResult() + + for stream_name in streams: + stream_preview = self.preview_stream( + ab_source, stream_name, sample_size + ) + result.streams.append(stream_preview) + + # Totals are less relevant with unknown counts, but we act best effort + if stream_preview.schema_changes: + result.has_schema_changes = True + + return result + diff --git a/src/brickbyte/types.py b/src/brickbyte/types.py index 12a49e1..b5ce350 100644 --- a/src/brickbyte/types.py +++ b/src/brickbyte/types.py @@ -104,7 +104,4 @@ "source-zendesk-chat", "source-zendesk-support", "source-zenloop" -] - - -Destination = Literal["destination-databricks"] \ No newline at end of file +] \ No newline at end of file diff --git a/src/brickbyte/writers/__init__.py b/src/brickbyte/writers/__init__.py new file mode 100644 index 0000000..9a0a4ed --- /dev/null +++ b/src/brickbyte/writers/__init__.py @@ -0,0 +1,101 @@ +""" +Brickbyte Writers Module. +""" +import logging +from typing import Optional, Union + +from brickbyte.writers.base import BaseWriter +from brickbyte.writers.spark_streaming_writer import SparkStreamingWriter +from brickbyte.writers.sql_streaming_writer import SQLStreamingWriter + +logger = logging.getLogger(__name__) + +def create_streaming_writer( + catalog: str, + schema: str, + staging_volume: Optional[str] = None, + warehouse_id: Optional[str] = None, + force_sql: bool = False, + buffer_size_records: int = 50000, + buffer_size_mb: int = 100, + flatten: bool = False, +) -> Union[SparkStreamingWriter, SQLStreamingWriter]: + """ + Create a streaming writer based on environment. + + Logic: + 1. If Spark is active (and not force_sql=True) -> SparkStreamingWriter (No Volume needed). + 2. Else -> SQLStreamingWriter (Volume REQUIRED). + """ + + # 1. Attempt to detect Spark + spark_active = False + if not force_sql: + try: + from pyspark.sql import SparkSession + if SparkSession.getActiveSession(): + spark_active = True + except ImportError: + pass + + if spark_active: + logger.info("Spark detected. Using Native Spark Streaming Writer.") + return SparkStreamingWriter( + catalog=catalog, + schema=schema, + buffer_size_records=buffer_size_records, + buffer_size_mb=buffer_size_mb, + flatten=flatten, + ) + + # 2. Fallback to SQL Writer + logger.info("Spark not active (or forced off). Using SQL Streaming Writer.") + + if not staging_volume: + raise ValueError( + "staging_volume is REQUIRED when running outside of Databricks (or when forcing SQL mode). " + "Because we cannot access local disk from the warehouse, we must stage files in a Volume." + ) + + from databricks.sdk import WorkspaceClient + + w = WorkspaceClient() + server_hostname = w.config.host.replace("https://", "").rstrip("/") + access_token = w.config.token + + # Auto-discover warehouse if not provided + if not warehouse_id: + warehouses = list(w.warehouses.list()) + running = [ + wh for wh in warehouses + if wh.state and wh.state.value == "RUNNING" + ] + if running: + warehouse_id = running[0].id + else: + raise ValueError( + "No running SQL warehouse found. " + "Specify warehouse_id or start a warehouse." + ) + + http_path = f"/sql/1.0/warehouses/{warehouse_id}" + + return SQLStreamingWriter( + catalog=catalog, + schema=schema, + staging_volume=staging_volume, + server_hostname=server_hostname, + http_path=http_path, + access_token=access_token, + buffer_size_records=buffer_size_records, + buffer_size_mb=buffer_size_mb, + flatten=flatten, + ) + + +__all__ = [ + "BaseWriter", + "SparkStreamingWriter", + "SQLStreamingWriter", + "create_streaming_writer", +] diff --git a/src/brickbyte/writers/base.py b/src/brickbyte/writers/base.py new file mode 100644 index 0000000..25c9d29 --- /dev/null +++ b/src/brickbyte/writers/base.py @@ -0,0 +1,65 @@ +""" +Abstract base writer for BrickByte. +Defines the interface all writers must implement. +""" +from abc import ABC, abstractmethod +from typing import Dict, Optional + + +class BaseWriter(ABC): + """ + Abstract base class for all BrickByte writers. + + Writers handle writing data from PyAirbyte cache to Databricks. + """ + + def __init__(self, catalog: str, schema: str): + """ + Initialize the writer. + + Args: + catalog: Unity Catalog name + schema: Target schema name + """ + self.catalog = catalog + self.schema = schema + + def get_table_name(self, stream_name: str) -> str: + """Get fully qualified table name for a stream.""" + return f"{self.catalog}.{self.schema}.{stream_name}" + + @abstractmethod + def table_exists(self, stream_name: str) -> bool: + """Check if a table exists.""" + pass + + @abstractmethod + def get_table_schema(self, stream_name: str) -> Optional[Dict[str, str]]: + """ + Get schema of an existing table. + + Returns: + Dict mapping column names to types, or None if table doesn't exist + """ + pass + + @abstractmethod + def drop_table(self, stream_name: str): + """Drop a table if it exists.""" + pass + + @abstractmethod + def write_record(self, stream_name: str, record: dict): + """Buffer a single record for writing.""" + pass + + @abstractmethod + def flush_stream(self, stream_name: str): + """Flush buffered records for a specific stream.""" + pass + + @abstractmethod + def close(self): + """Flush all buffers and clean up resources.""" + pass + diff --git a/src/brickbyte/writers/spark_streaming_writer.py b/src/brickbyte/writers/spark_streaming_writer.py new file mode 100644 index 0000000..e93e746 --- /dev/null +++ b/src/brickbyte/writers/spark_streaming_writer.py @@ -0,0 +1,159 @@ +""" +Spark Streaming writer for Brickbyte using native Databricks/Spark execution. + +Uses micro-batch streaming for: +- Bounded memory usage (flushes at configurable thresholds) +- Fault tolerance (each flush = implicit checkpoint) +- Databricks auto-optimize handles small file compaction +""" +import json +import logging +import sys +from datetime import datetime +from typing import Dict, List, Optional +from uuid import uuid4 + +from brickbyte.writers.base import BaseWriter + +logger = logging.getLogger(__name__) + + +class SparkStreamingWriter(BaseWriter): + """ + Writes data to Databricks using micro-batch streaming. + + Each flush writes to Delta immediately, providing: + - Implicit checkpointing (resume from last successful batch on failure) + - Bounded memory (configurable batch size) + - Databricks auto-optimize handles small file compaction + """ + + def __init__( + self, + catalog: str, + schema: str, + buffer_size_records: int = 50000, + buffer_size_mb: int = 100, + flatten: bool = False, + ): + """ + Initialize Spark Streaming Writer. + + Args: + catalog: Unity Catalog name + schema: Target schema name + buffer_size_records: Records per micro-batch (default: 50k) + buffer_size_mb: Max batch size in MB (default: 100MB) + flatten: If True, flatten record fields into columns (default: False) + """ + super().__init__(catalog, schema) + self.buffer_size_records = buffer_size_records + self.buffer_size_bytes = buffer_size_mb * 1024 * 1024 + self.flatten = flatten + + self._spark = None + self._buffers: Dict[str, List[dict]] = {} + self._buffer_counts: Dict[str, int] = {} + self._buffer_sizes: Dict[str, int] = {} + + @property + def spark(self): + """Get or create Spark session.""" + if self._spark is None: + from pyspark.sql import SparkSession + self._spark = SparkSession.builder.getOrCreate() + return self._spark + + def table_exists(self, stream_name: str) -> bool: + """Check if a table exists.""" + table_name = self.get_table_name(stream_name) + return self.spark.catalog.tableExists(table_name) + + def get_table_schema(self, stream_name: str) -> Optional[Dict[str, str]]: + """Get schema of an existing table.""" + if not self.table_exists(stream_name): + return None + + table_name = self.get_table_name(stream_name) + df = self.spark.table(table_name) + return {f.name: str(f.dataType) for f in df.schema.fields} + + def drop_table(self, stream_name: str): + """Drop a table if it exists.""" + table_name = self.get_table_name(stream_name) + self.spark.sql(f"DROP TABLE IF EXISTS {table_name}") + + def _transform_record(self, record: dict) -> dict: + """Transform record based on flatten mode.""" + if self.flatten: + # Flattened: all fields as top-level columns + metadata + transformed = dict(record) + transformed["_id"] = str(uuid4()) + transformed["_extracted_at"] = datetime.now() + return transformed + else: + # Raw: 3 columns with JSON blob + return { + "id": str(uuid4()), + "extracted_at": datetime.now(), + "data": json.dumps(record, default=str) + } + + def write_record(self, stream_name: str, record: dict): + """Buffer a single record.""" + if stream_name not in self._buffers: + self._buffers[stream_name] = [] + self._buffer_counts[stream_name] = 0 + self._buffer_sizes[stream_name] = 0 + + transformed = self._transform_record(record) + self._buffers[stream_name].append(transformed) + self._buffer_counts[stream_name] += 1 + + # Estimate size based on data field or full record + if self.flatten: + self._buffer_sizes[stream_name] += sys.getsizeof(str(transformed)) + else: + self._buffer_sizes[stream_name] += sys.getsizeof(transformed.get("data", "")) + + # Flush micro-batch when thresholds hit + if (self._buffer_counts[stream_name] >= self.buffer_size_records or + self._buffer_sizes[stream_name] >= self.buffer_size_bytes): + self._write_micro_batch(stream_name) + + def _write_micro_batch(self, stream_name: str): + """Write a micro-batch to Delta (each call = implicit checkpoint).""" + if stream_name not in self._buffers or not self._buffers[stream_name]: + return + + records = self._buffers[stream_name] + batch_count = len(records) + table_name = self.get_table_name(stream_name) + + try: + df = self.spark.createDataFrame(records) + (df.write + .format("delta") + .mode("append") + .option("mergeSchema", "true") + .saveAsTable(table_name)) + + logger.debug("Wrote %d records to %s", batch_count, table_name) + + except Exception as e: + logger.error("Error writing batch for %s: %s", stream_name, e) + raise + + # Reset buffer + self._buffers[stream_name] = [] + self._buffer_counts[stream_name] = 0 + self._buffer_sizes[stream_name] = 0 + + def flush_stream(self, stream_name: str): + """Flush any remaining buffered records to Delta.""" + self._write_micro_batch(stream_name) + + def close(self): + """Flush all remaining buffers.""" + for stream_name in self._buffers: + self.flush_stream(stream_name) diff --git a/src/brickbyte/writers/sql_streaming_writer.py b/src/brickbyte/writers/sql_streaming_writer.py new file mode 100644 index 0000000..2b499d9 --- /dev/null +++ b/src/brickbyte/writers/sql_streaming_writer.py @@ -0,0 +1,242 @@ +""" +SQL Streaming writer for Brickbyte using PyArrow buffering and COPY INTO. + +Uses micro-batch streaming for: +- Bounded memory usage (flushes at configurable thresholds) +- Fault tolerance (each flush = implicit checkpoint) +- Databricks auto-optimize handles small file compaction +""" +import json +import logging +import os +import sys +from datetime import datetime +from typing import Dict, List, Optional +from uuid import uuid4 + +import pyarrow as pa +import pyarrow.parquet as pq + +from brickbyte.writers.base import BaseWriter + +logger = logging.getLogger(__name__) + + +class SQLStreamingWriter(BaseWriter): + """ + Writes data to Databricks using micro-batch streaming via SQL Connector. + + Each flush writes to Delta immediately via COPY INTO, providing: + - Implicit checkpointing (resume from last successful batch on failure) + - Bounded memory (configurable batch size) + - Databricks auto-optimize handles small file compaction + """ + + def __init__( + self, + catalog: str, + schema: str, + staging_volume: str, + server_hostname: str, + http_path: str, + access_token: str, + buffer_size_records: int = 50000, + buffer_size_mb: int = 100, + flatten: bool = False, + ): + """ + Initialize SQL Streaming Writer. + + Args: + catalog: Unity Catalog name + schema: Target schema name + staging_volume: Unity Catalog Volume path for staging parquet files + server_hostname: Databricks server hostname + http_path: SQL Warehouse HTTP path + access_token: Databricks access token + buffer_size_records: Records per micro-batch (default: 50k) + buffer_size_mb: Max batch size in MB (default: 100MB) + flatten: If True, flatten record fields into columns (default: False) + """ + super().__init__(catalog, schema) + self.staging_volume = staging_volume + self.server_hostname = server_hostname + self.http_path = http_path + self._access_token = access_token + self.flatten = flatten + + self.buffer_size_records = buffer_size_records + self.buffer_size_bytes = buffer_size_mb * 1024 * 1024 + + self._connection = None + self._buffers: Dict[str, List[dict]] = {} + self._buffer_counts: Dict[str, int] = {} + self._buffer_sizes: Dict[str, int] = {} + + parts = self.staging_volume.split(".") + if len(parts) != 3: + raise ValueError( + f"staging_volume must be in format 'catalog.schema.volume_name', " + f"got: {self.staging_volume}" + ) + self._vol_subpath = os.path.join(parts[0], parts[1], parts[2]) + + def _get_connection(self): + """Get or create database connection.""" + if self._connection is None: + from databricks import sql + self._connection = sql.connect( + server_hostname=self.server_hostname, + http_path=self.http_path, + access_token=self._access_token, + catalog=self.catalog, + schema=self.schema, + ) + return self._connection + + def _execute(self, query: str): + """Execute a SQL query.""" + conn = self._get_connection() + cursor = conn.cursor() + try: + cursor.execute(query) + finally: + cursor.close() + + def _get_staging_dir(self, stream_name: str) -> str: + """Get staging directory path in Volume.""" + base_path = f"/Volumes/{self._vol_subpath}" + stream_dir = os.path.join(base_path, "brickbyte_streaming", stream_name) + os.makedirs(stream_dir, exist_ok=True) + return stream_dir + + def table_exists(self, stream_name: str) -> bool: + """Check if a table exists.""" + table_name = self.get_table_name(stream_name) + try: + self._execute(f"DESCRIBE TABLE {table_name}") + return True + except Exception: + return False + + def get_table_schema(self, stream_name: str) -> Optional[Dict[str, str]]: + """Get schema of an existing table.""" + if not self.table_exists(stream_name): + return None + + table_name = self.get_table_name(stream_name) + conn = self._get_connection() + cursor = conn.cursor() + cursor.execute(f"DESCRIBE TABLE {table_name}") + results = cursor.fetchall() + cursor.close() + return {row[0]: row[1] for row in results} + + def drop_table(self, stream_name: str): + """Drop a table if it exists.""" + table_name = self.get_table_name(stream_name) + self._execute(f"DROP TABLE IF EXISTS {table_name}") + + def _transform_record(self, record: dict) -> dict: + """Transform record based on flatten mode.""" + if self.flatten: + # Flattened: all fields as top-level columns + metadata + transformed = dict(record) + transformed["_id"] = str(uuid4()) + transformed["_extracted_at"] = datetime.now() + return transformed + else: + # Raw: 3 columns with JSON blob + return { + "id": str(uuid4()), + "extracted_at": datetime.now(), + "data": json.dumps(record, default=str) + } + + def write_record(self, stream_name: str, record: dict): + """Buffer a single record.""" + if stream_name not in self._buffers: + self._buffers[stream_name] = [] + self._buffer_counts[stream_name] = 0 + self._buffer_sizes[stream_name] = 0 + + transformed = self._transform_record(record) + self._buffers[stream_name].append(transformed) + self._buffer_counts[stream_name] += 1 + + # Estimate size based on data field or full record + if self.flatten: + self._buffer_sizes[stream_name] += sys.getsizeof(str(transformed)) + else: + self._buffer_sizes[stream_name] += sys.getsizeof(transformed.get("data", "")) + + # Check both thresholds + if (self._buffer_counts[stream_name] >= self.buffer_size_records or + self._buffer_sizes[stream_name] >= self.buffer_size_bytes): + self.flush_stream(stream_name) + + def flush_stream(self, stream_name: str): + """Flush buffer for a specific stream.""" + if stream_name not in self._buffers or not self._buffers[stream_name]: + return + + records = self._buffers[stream_name] + + try: + table = pa.Table.from_pylist(records) + + staging_dir = self._get_staging_dir(stream_name) + filename = f"batch_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.parquet" + file_path = os.path.join(staging_dir, filename) + + pq.write_table(table, file_path, compression='zstd') + + table_name = self.get_table_name(stream_name) + + # For raw mode, create table with known schema + # For flatten mode, rely on COPY INTO with mergeSchema + if not self.flatten: + create_query = f""" + CREATE TABLE IF NOT EXISTS {table_name} ( + id STRING, + extracted_at TIMESTAMP, + data STRING + ) + """ + self._execute(create_query) + + copy_query = f""" + COPY INTO {table_name} + FROM '{file_path}' + FILEFORMAT = PARQUET + FORMAT_OPTIONS ('mergeSchema' = 'true') + COPY_OPTIONS ('force' = 'true') + """ + self._execute(copy_query) + + os.remove(file_path) + + except Exception as e: + logger.error(f"Error flushing stream {stream_name}: {e}") + raise + + # Reset buffer + self._buffers[stream_name] = [] + self._buffer_counts[stream_name] = 0 + self._buffer_sizes[stream_name] = 0 + + def close(self): + """Flush all remaining buffers and close connection.""" + for stream_name in list(self._buffers.keys()): + self.flush_stream(stream_name) + + try: + staging_dir = self._get_staging_dir(stream_name) + if os.path.exists(staging_dir): + os.rmdir(staging_dir) + except Exception: + pass + + if self._connection: + self._connection.close() + self._connection = None diff --git a/tests/test_base_writer.py b/tests/test_base_writer.py new file mode 100644 index 0000000..fa8b646 --- /dev/null +++ b/tests/test_base_writer.py @@ -0,0 +1,199 @@ +""" +Tests for BaseWriter abstract class and common functionality. +""" +import json +from datetime import datetime +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte.writers.base import BaseWriter +from brickbyte.writers.spark_streaming_writer import SparkStreamingWriter +from brickbyte.writers.sql_streaming_writer import SQLStreamingWriter + + +class TestBaseWriter: + """Test BaseWriter abstract class.""" + + def test_cannot_instantiate_directly(self): + """Test that BaseWriter cannot be instantiated directly.""" + with pytest.raises(TypeError, match="Can't instantiate abstract class"): + BaseWriter(catalog="main", schema="test") + + def test_get_table_name(self): + """Test get_table_name via concrete implementation.""" + with patch("databricks.sql.connect"): + writer = SQLStreamingWriter( + catalog="main", + schema="bronze", + staging_volume="a.b.c", + server_hostname="h", + http_path="p", + access_token="t", + ) + + assert writer.get_table_name("users") == "main.bronze.users" + assert writer.get_table_name("my_table") == "main.bronze.my_table" + + +class TestTransformRecord: + """Test _transform_record across implementations.""" + + @pytest.fixture + def spark_writer(self, tmp_path): + import os + with patch.dict(os.environ, {"SPARK_LOCAL_DIRS": str(tmp_path)}): + writer = SparkStreamingWriter(catalog="main", schema="test") + writer._spark = MagicMock() + return writer + + @pytest.fixture + def sql_writer(self): + with patch("databricks.sql.connect"): + return SQLStreamingWriter( + catalog="main", + schema="test", + staging_volume="a.b.c", + server_hostname="h", + http_path="p", + access_token="t", + ) + + def test_spark_transform_adds_metadata(self, spark_writer): + """Test SparkStreamingWriter adds Airbyte metadata.""" + record = {"id": 1, "email": "test@example.com"} + transformed = spark_writer._transform_record(record) + + assert "_airbyte_raw_id" in transformed + assert "_airbyte_extracted_at" in transformed + assert "_airbyte_data" in transformed + + def test_sql_transform_adds_metadata(self, sql_writer): + """Test SQLStreamingWriter adds Airbyte metadata.""" + record = {"id": 1, "email": "test@example.com"} + transformed = sql_writer._transform_record(record) + + assert "_airbyte_raw_id" in transformed + assert "_airbyte_extracted_at" in transformed + assert "_airbyte_data" in transformed + + def test_raw_id_is_uuid(self, spark_writer): + """Test that _airbyte_raw_id is a valid UUID.""" + record = {"id": 1} + transformed = spark_writer._transform_record(record) + + raw_id = transformed["_airbyte_raw_id"] + # UUID format: 8-4-4-4-12 hex digits + assert len(raw_id) == 36 + assert raw_id.count("-") == 4 + + def test_extracted_at_is_datetime(self, spark_writer): + """Test that _airbyte_extracted_at is a datetime.""" + record = {"id": 1} + transformed = spark_writer._transform_record(record) + + assert isinstance(transformed["_airbyte_extracted_at"], datetime) + + def test_data_is_json_string(self, spark_writer): + """Test that _airbyte_data is a valid JSON string.""" + record = {"id": 1, "nested": {"key": "value"}, "list": [1, 2, 3]} + transformed = spark_writer._transform_record(record) + + data_str = transformed["_airbyte_data"] + assert isinstance(data_str, str) + + # Should be valid JSON + parsed = json.loads(data_str) + assert parsed["id"] == 1 + assert parsed["nested"]["key"] == "value" + assert parsed["list"] == [1, 2, 3] + + def test_unique_raw_ids(self, spark_writer): + """Test that each transform generates unique raw_id.""" + record = {"id": 1} + + ids = set() + for _ in range(100): + transformed = spark_writer._transform_record(record) + ids.add(transformed["_airbyte_raw_id"]) + + assert len(ids) == 100 # All unique + + def test_transform_handles_special_characters(self, spark_writer): + """Test that transform handles special characters in data.""" + record = { + "text": 'Hello "world"', + "unicode": "ζ—₯本θͺž", + "newlines": "line1\nline2", + } + transformed = spark_writer._transform_record(record) + + parsed = json.loads(transformed["_airbyte_data"]) + assert parsed["text"] == 'Hello "world"' + assert parsed["unicode"] == "ζ—₯本θͺž" + assert parsed["newlines"] == "line1\nline2" + + def test_transform_handles_none_values(self, spark_writer): + """Test that transform handles None values.""" + record = {"id": 1, "optional": None} + transformed = spark_writer._transform_record(record) + + parsed = json.loads(transformed["_airbyte_data"]) + assert parsed["optional"] is None + + def test_transform_handles_empty_record(self, spark_writer): + """Test that transform handles empty records.""" + record = {} + transformed = spark_writer._transform_record(record) + + assert transformed["_airbyte_data"] == "{}" + + +class TestWriterConsistency: + """Test that both writers behave consistently.""" + + @pytest.fixture + def spark_writer(self, tmp_path): + import os + with patch.dict(os.environ, {"SPARK_LOCAL_DIRS": str(tmp_path)}): + writer = SparkStreamingWriter( + catalog="main", + schema="test", + buffer_size_records=100, + ) + writer._spark = MagicMock() + return writer + + @pytest.fixture + def sql_writer(self): + with patch("databricks.sql.connect"): + return SQLStreamingWriter( + catalog="main", + schema="test", + staging_volume="a.b.c", + server_hostname="h", + http_path="p", + access_token="t", + buffer_size_records=100, + ) + + def test_same_table_name_format(self, spark_writer, sql_writer): + """Test both writers generate same table names.""" + assert spark_writer.get_table_name("users") == sql_writer.get_table_name("users") + assert spark_writer.get_table_name("test") == sql_writer.get_table_name("test") + + def test_same_transform_schema(self, spark_writer, sql_writer): + """Test both writers produce same transformed schema.""" + record = {"id": 1, "name": "test"} + + spark_result = spark_writer._transform_record(record) + sql_result = sql_writer._transform_record(record) + + # Same keys + assert set(spark_result.keys()) == set(sql_result.keys()) + + # Same types + assert type(spark_result["_airbyte_raw_id"]) == type(sql_result["_airbyte_raw_id"]) + assert type(spark_result["_airbyte_extracted_at"]) == type(sql_result["_airbyte_extracted_at"]) + assert type(spark_result["_airbyte_data"]) == type(sql_result["_airbyte_data"]) + diff --git a/tests/test_buffer_thresholds.py b/tests/test_buffer_thresholds.py new file mode 100644 index 0000000..9c1de22 --- /dev/null +++ b/tests/test_buffer_thresholds.py @@ -0,0 +1,136 @@ +""" +Tests for buffer size thresholds (records AND bytes). +""" +import os +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte.writers.spark_streaming_writer import SparkStreamingWriter +from brickbyte.writers.sql_streaming_writer import SQLStreamingWriter + + +class TestBufferSizeBytes: + """Test byte-based buffer thresholds.""" + + @pytest.fixture + def spark_writer(self, tmp_path): + """SparkStreamingWriter with low byte threshold.""" + with patch.dict(os.environ, {"SPARK_LOCAL_DIRS": str(tmp_path)}): + writer = SparkStreamingWriter( + catalog="main", + schema="test", + buffer_size_records=1000, # High record limit + buffer_size_mb=1, # 1MB byte limit (will hit first) + ) + writer._spark = MagicMock() + writer._write_micro_batch = MagicMock() + return writer + + @pytest.fixture + def sql_writer(self): + """SQLStreamingWriter with low byte threshold.""" + with patch("databricks.sql.connect"): + writer = SQLStreamingWriter( + catalog="main", + schema="test", + staging_volume="main.staging.vol", + server_hostname="host", + http_path="/sql", + access_token="token", + buffer_size_records=1000, # High record limit + buffer_size_mb=1, # 1MB byte limit + ) + writer.flush_stream = MagicMock() + return writer + + def test_spark_flushes_on_byte_threshold(self, spark_writer): + """Test SparkStreamingWriter flushes when byte threshold is hit.""" + # sys.getsizeof returns ~50 bytes overhead per string + # So we need many medium-sized records to exceed 1MB + # 1MB = 1,048,576 bytes / ~100 bytes per record = ~10k records + # Use 20k char string to make each record ~20KB + large_data = "x" * 20_000 + + # Write records until we exceed threshold + for i in range(40): # 40 * ~25KB = ~1MB + spark_writer.write_record("stream1", {"data": large_data, "i": i}) + + assert spark_writer._write_micro_batch.call_count == 0 + + # One more should trigger flush + for i in range(20): + spark_writer.write_record("stream1", {"data": large_data, "i": i}) + + assert spark_writer._write_micro_batch.call_count >= 1 + + def test_sql_flushes_on_byte_threshold(self, sql_writer): + """Test SQLStreamingWriter flushes when byte threshold is hit.""" + large_data = "x" * 20_000 + + for i in range(40): + sql_writer.write_record("stream1", {"data": large_data, "i": i}) + + assert sql_writer.flush_stream.call_count == 0 + + for i in range(20): + sql_writer.write_record("stream1", {"data": large_data, "i": i}) + + assert sql_writer.flush_stream.call_count >= 1 + + def test_record_threshold_still_works(self, tmp_path): + """Test that record count threshold works independently.""" + with patch.dict(os.environ, {"SPARK_LOCAL_DIRS": str(tmp_path)}): + writer = SparkStreamingWriter( + catalog="main", + schema="test", + buffer_size_records=2, # Low record limit + buffer_size_mb=1000, # High byte limit (won't hit) + ) + writer._spark = MagicMock() + writer._write_micro_batch = MagicMock() + + # Small records - byte threshold won't be hit + writer.write_record("stream1", {"id": 1}) + assert writer._write_micro_batch.call_count == 0 + + writer.write_record("stream1", {"id": 2}) + assert writer._write_micro_batch.call_count == 1 + + def test_buffer_size_tracking(self, spark_writer): + """Test that buffer sizes are tracked correctly.""" + spark_writer._write_micro_batch = MagicMock() # Prevent actual flush + + spark_writer.write_record("stream1", {"data": "small"}) + + assert spark_writer._buffer_sizes["stream1"] > 0 + initial_size = spark_writer._buffer_sizes["stream1"] + + spark_writer.write_record("stream1", {"data": "another"}) + + assert spark_writer._buffer_sizes["stream1"] > initial_size + + def test_buffer_reset_after_flush(self, tmp_path): + """Test that all buffer tracking is reset after flush.""" + with patch.dict(os.environ, {"SPARK_LOCAL_DIRS": str(tmp_path)}): + writer = SparkStreamingWriter( + catalog="main", + schema="test", + buffer_size_records=2, + buffer_size_mb=100, + ) + writer._spark = MagicMock() + + # Mock the write operations + with patch("pyarrow.parquet.write_table"), patch("os.remove"): + mock_df = MagicMock() + writer._spark.read.parquet.return_value = mock_df + + writer.write_record("stream1", {"id": 1}) + writer.write_record("stream1", {"id": 2}) # Triggers flush + + # All tracking should be reset + assert writer._buffers["stream1"] == [] + assert writer._buffer_counts["stream1"] == 0 + assert writer._buffer_sizes["stream1"] == 0 + diff --git a/tests/test_credentials.py b/tests/test_credentials.py new file mode 100644 index 0000000..3efcdb5 --- /dev/null +++ b/tests/test_credentials.py @@ -0,0 +1,314 @@ +"""Tests for credential resolution.""" +import pytest +from unittest.mock import MagicMock, patch + +# Import credentials module directly to avoid virtualenv import +from brickbyte.credentials import CredentialResolver + + +class TestCredentialResolver: + """Tests for CredentialResolver class.""" + + def test_init_default_scope(self): + """Test default secrets scope is 'brickbyte'.""" + resolver = CredentialResolver() + assert resolver.secrets_scope == "brickbyte" + + def test_init_custom_scope(self): + """Test custom secrets scope.""" + resolver = CredentialResolver(secrets_scope="custom-scope") + assert resolver.secrets_scope == "custom-scope" + + def test_merge_credentials_no_discovered(self): + """Test merge when no credentials discovered.""" + resolver = CredentialResolver() + + source_config = {"bucket": "my-bucket", "region": "us-east-1"} + result = resolver.merge_credentials("source-s3", source_config) + + # Should return original config unchanged + assert result == source_config + + def test_merge_credentials_with_discovered(self): + """Test merge with discovered credentials.""" + resolver = CredentialResolver() + # Manually inject cached credentials + resolver._cache["source-s3"] = { + "aws_access_key_id": "discovered_key", + "aws_secret_access_key": "discovered_secret", + } + + source_config = {"bucket": "my-bucket"} + result = resolver.merge_credentials("source-s3", source_config) + + # Should merge discovered credentials + assert result["bucket"] == "my-bucket" + assert result["aws_access_key_id"] == "discovered_key" + assert result["aws_secret_access_key"] == "discovered_secret" + + def test_merge_credentials_explicit_override(self): + """Test that explicit config overrides discovered credentials.""" + resolver = CredentialResolver() + # Manually inject cached credentials + resolver._cache["source-s3"] = { + "aws_access_key_id": "discovered_key", + "aws_secret_access_key": "discovered_secret", + "region_name": "us-west-2", + } + + source_config = { + "bucket": "my-bucket", + "aws_access_key_id": "explicit_key", # Override + } + result = resolver.merge_credentials("source-s3", source_config) + + # Explicit value should override discovered + assert result["aws_access_key_id"] == "explicit_key" + # Non-overridden discovered value should remain + assert result["aws_secret_access_key"] == "discovered_secret" + assert result["region_name"] == "us-west-2" + assert result["bucket"] == "my-bucket" + + def test_deep_merge_nested_dicts(self): + """Test deep merge with nested dictionaries.""" + resolver = CredentialResolver() + + base = { + "credentials": { + "client_id": "base_id", + "client_secret": "base_secret", + }, + "other": "value", + } + override = { + "credentials": { + "client_id": "override_id", + }, + "bucket": "my-bucket", + } + + result = resolver._deep_merge(base, override) + + assert result["credentials"]["client_id"] == "override_id" + assert result["credentials"]["client_secret"] == "base_secret" + assert result["other"] == "value" + assert result["bucket"] == "my-bucket" + + def test_validate_with_credentials(self): + """Test validate returns True when credentials exist.""" + resolver = CredentialResolver() + resolver._cache["source-s3"] = {"aws_access_key_id": "key"} + + assert resolver.validate("source-s3") is True + + def test_validate_without_credentials(self): + """Test validate returns False when no credentials exist.""" + resolver = CredentialResolver() + + assert resolver.validate("source-nonexistent") is False + + def test_clear_cache(self): + """Test cache clearing.""" + resolver = CredentialResolver() + resolver._cache["source-s3"] = {"key": "value"} + resolver._available_keys = ["source-s3/key"] + + resolver.clear_cache() + + assert resolver._cache == {} + assert resolver._available_keys is None + + +class TestCredentialResolverWithMockedDbutils: + """Tests with mocked dbutils.""" + + def test_list_secrets_for_source(self): + """Test listing secrets for a specific source.""" + resolver = CredentialResolver() + + # Mock dbutils + mock_dbutils = MagicMock() + mock_secret1 = MagicMock() + mock_secret1.key = "source-s3/aws_access_key_id" + mock_secret2 = MagicMock() + mock_secret2.key = "source-s3/aws_secret_access_key" + mock_secret3 = MagicMock() + mock_secret3.key = "source-gcs/service_account" + + mock_dbutils.secrets.list.return_value = [mock_secret1, mock_secret2, mock_secret3] + resolver._dbutils = mock_dbutils + + keys = resolver._list_secrets_for_source("source-s3") + + assert "aws_access_key_id" in keys + assert "aws_secret_access_key" in keys + assert "service_account" not in keys + + def test_get_secret(self): + """Test getting a single secret.""" + resolver = CredentialResolver() + + mock_dbutils = MagicMock() + mock_dbutils.secrets.get.return_value = "secret_value" + resolver._dbutils = mock_dbutils + + value = resolver._get_secret("source-s3/aws_access_key_id") + + assert value == "secret_value" + mock_dbutils.secrets.get.assert_called_once_with( + scope="brickbyte", + key="source-s3/aws_access_key_id" + ) + + def test_list_available_sources(self): + """Test listing all available sources.""" + resolver = CredentialResolver() + + mock_dbutils = MagicMock() + mock_secrets = [] + for key in ["source-s3/key1", "source-s3/key2", "source-gcs/key1", "source-teams/key1"]: + mock_secret = MagicMock() + mock_secret.key = key + mock_secrets.append(mock_secret) + + mock_dbutils.secrets.list.return_value = mock_secrets + resolver._dbutils = mock_dbutils + + sources = resolver.list_available_sources() + + assert "source-s3" in sources + assert "source-gcs" in sources + assert "source-teams" in sources + + def test_get_credentials_convention_based(self): + """Test getting credentials via convention-based discovery.""" + resolver = CredentialResolver() + + mock_dbutils = MagicMock() + mock_secret1 = MagicMock() + mock_secret1.key = "source-s3/aws_access_key_id" + mock_secret2 = MagicMock() + mock_secret2.key = "source-s3/aws_secret_access_key" + + mock_dbutils.secrets.list.return_value = [mock_secret1, mock_secret2] + mock_dbutils.secrets.get.side_effect = lambda scope, key: { + "source-s3/aws_access_key_id": "key123", + "source-s3/aws_secret_access_key": "secret456", + }.get(key) + + resolver._dbutils = mock_dbutils + + creds = resolver.get_credentials("source-s3") + + assert creds["aws_access_key_id"] == "key123" + assert creds["aws_secret_access_key"] == "secret456" + + +class TestYamlProfiles: + """Tests for YAML profile loading.""" + + def test_resolve_profile_simple(self): + """Test resolving a simple profile without secret references.""" + resolver = CredentialResolver() + resolver._profiles = { + "test-profile": { + "region": "us-east-1", + "bucket": "my-bucket", + } + } + + result = resolver._resolve_profile("test-profile") + + assert result["region"] == "us-east-1" + assert result["bucket"] == "my-bucket" + + def test_resolve_profile_nonexistent(self): + """Test resolving a nonexistent profile returns empty dict.""" + resolver = CredentialResolver() + resolver._profiles = {} + + result = resolver._resolve_profile("nonexistent") + + assert result == {} + + def test_mappings_take_precedence(self): + """Test that profile mappings are used when available.""" + resolver = CredentialResolver() + resolver._profiles = { + "azure-shared": { + "tenant_id": "tenant123", + "client_id": "client456", + } + } + resolver._mappings = { + "source-microsoft-teams": "azure-shared", + } + + creds = resolver.get_credentials("source-microsoft-teams") + + assert creds["tenant_id"] == "tenant123" + assert creds["client_id"] == "client456" + + +def _has_virtualenv(): + """Check if virtualenv is available.""" + try: + import virtualenv + return True + except ImportError: + return False + + +@pytest.mark.skipif(not _has_virtualenv(), reason="virtualenv not installed") +class TestBrickbyteCredentialIntegration: + """Tests for Brickbyte credential integration.""" + + def test_brickbyte_init_with_default_scope(self): + """Test Brickbyte initializes credential resolver with default scope.""" + from brickbyte import Brickbyte + + bb = Brickbyte() + + assert bb._credential_resolver.secrets_scope == "brickbyte" + + def test_brickbyte_init_with_custom_scope(self): + """Test Brickbyte with custom secrets scope.""" + from brickbyte import Brickbyte + + bb = Brickbyte(secrets_scope="my-custom-scope") + + assert bb._credential_resolver.secrets_scope == "my-custom-scope" + + def test_list_configured_sources(self): + """Test listing configured sources.""" + from brickbyte import Brickbyte + + bb = Brickbyte() + bb._credential_resolver._cache = { + "source-s3": {"key": "value"}, + } + bb._credential_resolver._available_keys = ["source-s3/key", "source-gcs/key"] + + # Mock dbutils to return the cached keys + mock_dbutils = MagicMock() + mock_s3 = MagicMock() + mock_s3.key = "source-s3/key" + mock_gcs = MagicMock() + mock_gcs.key = "source-gcs/key" + mock_dbutils.secrets.list.return_value = [mock_s3, mock_gcs] + bb._credential_resolver._dbutils = mock_dbutils + + sources = bb.list_configured_sources() + + assert "source-s3" in sources + assert "source-gcs" in sources + + def test_validate_credentials(self): + """Test credential validation.""" + from brickbyte import Brickbyte + + bb = Brickbyte() + bb._credential_resolver._cache["source-s3"] = {"key": "value"} + + assert bb.validate_credentials("source-s3") is True + assert bb.validate_credentials("source-nonexistent") is False diff --git a/tests/test_functional.py b/tests/test_functional.py new file mode 100644 index 0000000..08ce982 --- /dev/null +++ b/tests/test_functional.py @@ -0,0 +1,52 @@ +""" +Verification tests for Brickbyte functionalities (Streaming Only). +""" +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte import Brickbyte + + +class TestBrickbyteFunctional: + + @pytest.fixture + def mock_airbyte(self): + import sys + mock_ab = MagicMock() + with patch.dict(sys.modules, {"airbyte": mock_ab}): + yield mock_ab + + @pytest.fixture + def brickbyte(self, tmp_path): + return Brickbyte(base_venv_directory=str(tmp_path)) + + def test_sync_streaming_default(self, brickbyte, mock_airbyte): + """Test the sync method with default streaming behavior.""" + # Mock dependencies + mock_source = MagicMock() + mock_airbyte.get_source.return_value = mock_source + mock_source.get_selected_streams.return_value = ["test_stream"] + + # Mock records generator + mock_source.get_records.return_value = [{"id": 1, "val": "a"}, {"id": 2, "val": "b"}] + + # Mock writer factory + with patch("brickbyte.writers.create_streaming_writer") as mock_create_writer: + mock_writer = MagicMock() + mock_create_writer.return_value = mock_writer + + result = brickbyte.sync( + source="source-faker", + source_config={}, + catalog="main", + schema="test", + staging_volume="main.staging.vol" + ) + + # Verifications + assert result.records_written == 2 + mock_create_writer.assert_called_once() + assert mock_writer.write_record.call_count == 2 + mock_writer.flush_stream.assert_called_with("test_stream") + mock_writer.close.assert_called_once() diff --git a/tests/test_hybrid.py b/tests/test_hybrid.py new file mode 100644 index 0000000..25a03d4 --- /dev/null +++ b/tests/test_hybrid.py @@ -0,0 +1,67 @@ +""" +Tests for Hybrid Streaming Architecture (Spark vs SQL). +""" +import sys +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte.writers import SparkStreamingWriter, SQLStreamingWriter, create_streaming_writer + + +class TestHybridFactory: + + @patch("os.makedirs") + def test_factory_detects_spark(self, mock_makedirs): + """Test that SparkStreamingWriter is created when Spark is active.""" + + # Mock pyspark.sql.SparkSession.getActiveSession + with patch.dict(sys.modules, {"pyspark.sql": MagicMock()}): + mock_session = MagicMock() + sys.modules["pyspark.sql"].SparkSession.getActiveSession.return_value = mock_session + + writer = create_streaming_writer( + catalog="main", + schema="test" + ) + + assert isinstance(writer, SparkStreamingWriter) + assert writer.catalog == "main" + + def test_factory_fallback_to_sql(self): + """Test that SQLStreamingWriter is created when Spark is missing.""" + + # Simulate import error for pyspark + with patch.dict(sys.modules, {"pyspark.sql": None}): + # We also need to mock databricks.sdk + with patch("databricks.sdk.WorkspaceClient") as mock_ws_client: + mock_w = MagicMock() + mock_ws_client.return_value = mock_w + mock_w.config.host = "https://test-host" + mock_w.config.token = "token" + + # Mock warehouse listing + mock_wh = MagicMock() + mock_wh.state.value = "RUNNING" + mock_wh.id = "wh-123" + mock_w.warehouses.list.return_value = [mock_wh] + + writer = create_streaming_writer( + catalog="main", + schema="test", + staging_volume="main.test.vol" + ) + + assert isinstance(writer, SQLStreamingWriter) + assert writer.staging_volume == "main.test.vol" + + def test_factory_raises_error_no_volume_no_spark(self): + """Test that ValueError is raised if no Spark and no Volume.""" + + with patch.dict(sys.modules, {"pyspark.sql": None}): + with pytest.raises(ValueError, match="staging_volume is REQUIRED"): + create_streaming_writer( + catalog="main", + schema="test", + staging_volume=None + ) diff --git a/tests/test_mode_validation.py b/tests/test_mode_validation.py new file mode 100644 index 0000000..759b394 --- /dev/null +++ b/tests/test_mode_validation.py @@ -0,0 +1,143 @@ +""" +Tests for sync mode validation and overwrite behavior. +""" +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte import Brickbyte + + +class TestModeValidation: + """Test _validate_sync_params mode validation.""" + + @pytest.fixture + def brickbyte(self, tmp_path): + return Brickbyte(base_venv_directory=str(tmp_path)) + + def test_append_mode_valid(self, brickbyte): + """Test that append mode is valid.""" + # Should not raise + brickbyte._validate_sync_params(mode="append", staging_volume="a.b.c") + + def test_overwrite_mode_valid(self, brickbyte): + """Test that overwrite mode is valid.""" + # Should not raise + brickbyte._validate_sync_params(mode="overwrite", staging_volume="a.b.c") + + def test_merge_mode_not_implemented(self, brickbyte): + """Test that merge mode raises NotImplementedError.""" + with pytest.raises(NotImplementedError, match="Merge mode is not yet supported"): + brickbyte._validate_sync_params(mode="merge", staging_volume="a.b.c") + + def test_invalid_mode_raises_error(self, brickbyte): + """Test that invalid mode raises ValueError.""" + with pytest.raises(ValueError, match="Invalid mode 'invalid'"): + brickbyte._validate_sync_params(mode="invalid", staging_volume="a.b.c") + + def test_unknown_mode_raises_error(self, brickbyte): + """Test that unknown modes raise ValueError.""" + with pytest.raises(ValueError, match="Invalid mode"): + brickbyte._validate_sync_params(mode="upsert", staging_volume="a.b.c") + + +class TestOverwriteMode: + """Test overwrite mode behavior (drop_table before sync).""" + + @pytest.fixture + def mock_airbyte(self): + import sys + mock_ab = MagicMock() + with patch.dict(sys.modules, {"airbyte": mock_ab}): + yield mock_ab + + @pytest.fixture + def brickbyte(self, tmp_path): + return Brickbyte(base_venv_directory=str(tmp_path)) + + def test_overwrite_drops_table_before_streaming(self, brickbyte, mock_airbyte): + """Test that overwrite mode calls drop_table before streaming.""" + mock_source = MagicMock() + mock_airbyte.get_source.return_value = mock_source + mock_source.get_selected_streams.return_value = ["users", "orders"] + mock_source.get_records.return_value = [{"id": 1}] + + with patch("brickbyte.writers.create_streaming_writer") as mock_factory: + mock_writer = MagicMock() + mock_factory.return_value = mock_writer + + brickbyte.sync( + source="source-faker", + source_config={}, + catalog="main", + schema="test", + staging_volume="main.staging.vol", + mode="overwrite", + ) + + # Verify drop_table was called for each stream + assert mock_writer.drop_table.call_count == 2 + mock_writer.drop_table.assert_any_call("users") + mock_writer.drop_table.assert_any_call("orders") + + def test_append_does_not_drop_table(self, brickbyte, mock_airbyte): + """Test that append mode does NOT call drop_table.""" + mock_source = MagicMock() + mock_airbyte.get_source.return_value = mock_source + mock_source.get_selected_streams.return_value = ["users"] + mock_source.get_records.return_value = [{"id": 1}] + + with patch("brickbyte.writers.create_streaming_writer") as mock_factory: + mock_writer = MagicMock() + mock_factory.return_value = mock_writer + + brickbyte.sync( + source="source-faker", + source_config={}, + catalog="main", + schema="test", + staging_volume="main.staging.vol", + mode="append", + ) + + # Verify drop_table was NOT called + mock_writer.drop_table.assert_not_called() + + +class TestSyncModeIntegration: + """Integration tests for sync with different modes.""" + + @pytest.fixture + def mock_airbyte(self): + import sys + mock_ab = MagicMock() + with patch.dict(sys.modules, {"airbyte": mock_ab}): + yield mock_ab + + @pytest.fixture + def brickbyte(self, tmp_path): + return Brickbyte(base_venv_directory=str(tmp_path)) + + def test_default_mode_is_overwrite(self, brickbyte, mock_airbyte): + """Test that default mode is overwrite.""" + mock_source = MagicMock() + mock_airbyte.get_source.return_value = mock_source + mock_source.get_selected_streams.return_value = ["stream1"] + mock_source.get_records.return_value = [] + + with patch("brickbyte.writers.create_streaming_writer") as mock_factory: + mock_writer = MagicMock() + mock_factory.return_value = mock_writer + + # Call without specifying mode + brickbyte.sync( + source="source-faker", + source_config={}, + catalog="main", + schema="test", + staging_volume="main.staging.vol", + ) + + # Default is overwrite, so drop_table should be called + mock_writer.drop_table.assert_called_once_with("stream1") + diff --git a/tests/test_preview.py b/tests/test_preview.py new file mode 100644 index 0000000..cc0b200 --- /dev/null +++ b/tests/test_preview.py @@ -0,0 +1,269 @@ +""" +Tests for Preview Engine functionality. +""" +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte.preview import ( + PreviewEngine, + PreviewResult, + SchemaChange, + StreamPreview, +) + + +class TestSchemaChange: + """Test SchemaChange dataclass.""" + + def test_added_column_str(self): + """Test string representation of added column.""" + change = SchemaChange( + column="new_col", + change_type="added", + source_type="str", + ) + result = str(change) + assert "new_col" in result + assert "NEW" in result + assert "str" in result + + def test_removed_column_str(self): + """Test string representation of removed column.""" + change = SchemaChange( + column="old_col", + change_type="removed", + target_type="StringType", + ) + result = str(change) + assert "old_col" in result + assert "REMOVED" in result + + def test_type_changed_str(self): + """Test string representation of type change.""" + change = SchemaChange( + column="col", + change_type="type_changed", + source_type="int", + target_type="StringType", + ) + result = str(change) + assert "col" in result + assert "StringType" in result + assert "int" in result + + +class TestStreamPreview: + """Test StreamPreview dataclass.""" + + def test_str_with_counts(self): + """Test string representation with record counts.""" + preview = StreamPreview( + stream_name="users", + source_count=100, + target_count=50, + new_records=30, + modified_records=10, + deleted_records=5, + ) + result = str(preview) + assert "users" in result + assert "+30 new" in result + assert "~10 modified" in result + assert "-5 deleted" in result + + def test_str_streaming_unknown_count(self): + """Test string representation with unknown counts (streaming).""" + preview = StreamPreview( + stream_name="events", + source_count=-1, + target_count=0, + ) + result = str(preview) + assert "events" in result + assert "Unknown" in result or "Streaming" in result + + def test_str_with_schema_changes(self): + """Test string representation with schema changes.""" + preview = StreamPreview( + stream_name="orders", + source_count=50, + target_count=40, + schema_changes=[ + SchemaChange(column="new_field", change_type="added", source_type="str"), + ], + ) + result = str(preview) + assert "Schema changes" in result + assert "new_field" in result + + +class TestPreviewResult: + """Test PreviewResult dataclass.""" + + def test_str_output(self): + """Test complete preview result string output.""" + result = PreviewResult( + streams=[ + StreamPreview("users", 100, 50, new_records=30), + StreamPreview("orders", 200, 150, new_records=20), + ], + total_source_records=300, + total_new_records=50, + has_schema_changes=True, + ) + output = str(result) + + assert "Sync Preview" in output + assert "users" in output + assert "orders" in output + assert "Schema changes detected" in output + + def test_str_no_schema_changes(self): + """Test output without schema changes warning.""" + result = PreviewResult( + streams=[StreamPreview("data", 10, 10)], + has_schema_changes=False, + ) + output = str(result) + + assert "Schema changes detected" not in output + + +class TestPreviewEngine: + """Test PreviewEngine functionality.""" + + @pytest.fixture + def engine(self): + """Create a PreviewEngine with mocked Spark.""" + engine = PreviewEngine(catalog="main", schema="test") + engine._spark = MagicMock() + return engine + + def test_get_table_name(self, engine): + """Test fully qualified table name generation.""" + assert engine.get_table_name("users") == "main.test.users" + + def test_table_exists_true(self, engine): + """Test table_exists when table exists.""" + engine._spark.catalog.tableExists.return_value = True + assert engine.table_exists("users") is True + + def test_table_exists_false(self, engine): + """Test table_exists when table doesn't exist.""" + engine._spark.catalog.tableExists.return_value = False + assert engine.table_exists("users") is False + + def test_table_exists_no_spark(self): + """Test table_exists returns False when no Spark.""" + engine = PreviewEngine(catalog="main", schema="test") + # Directly set _spark to None and check behavior + # The spark property returns None when import fails + engine._spark = None + + # Mock the spark property to return None + with patch.object(PreviewEngine, 'spark', property(lambda self: None)): + assert engine.table_exists("users") is False + + def test_get_target_count(self, engine): + """Test getting target table count.""" + engine._spark.catalog.tableExists.return_value = True + mock_df = MagicMock() + mock_df.count.return_value = 42 + engine._spark.table.return_value = mock_df + + count = engine.get_target_count("users") + + assert count == 42 + + def test_get_target_count_no_table(self, engine): + """Test getting count when table doesn't exist.""" + engine._spark.catalog.tableExists.return_value = False + + count = engine.get_target_count("users") + + assert count == 0 + + def test_get_source_schema(self, engine): + """Test schema inference from sample records.""" + samples = [ + {"id": 1, "name": "test", "active": True, "score": 3.14}, + ] + + schema = engine.get_source_schema(samples) + + assert schema["id"] == "int" + assert schema["name"] == "str" + assert schema["active"] == "bool" + assert schema["score"] == "float" + + def test_get_source_schema_empty(self, engine): + """Test schema inference with empty samples.""" + schema = engine.get_source_schema([]) + assert schema == {} + + def test_compare_schemas_added_columns(self, engine): + """Test detecting added columns.""" + source = {"id": "int", "name": "str", "new_col": "str"} + target = {"id": "LongType", "name": "StringType"} + + changes = engine.compare_schemas(source, target) + + added = [c for c in changes if c.change_type == "added"] + assert len(added) == 1 + assert added[0].column == "new_col" + + def test_compare_schemas_removed_columns(self, engine): + """Test detecting removed columns.""" + source = {"id": "int"} + target = {"id": "LongType", "old_col": "StringType"} + + changes = engine.compare_schemas(source, target) + + removed = [c for c in changes if c.change_type == "removed"] + assert len(removed) == 1 + assert removed[0].column == "old_col" + + def test_compare_schemas_no_changes(self, engine): + """Test when schemas match.""" + source = {"id": "int", "name": "str"} + target = {"id": "LongType", "name": "StringType"} + + changes = engine.compare_schemas(source, target) + + # Same columns, different type names (expected) - no changes reported + assert len(changes) == 0 + + def test_preview_stream(self, engine): + """Test previewing a single stream.""" + engine._spark.catalog.tableExists.return_value = True + mock_df = MagicMock() + mock_df.count.return_value = 100 + engine._spark.table.return_value = mock_df + + # Mock source + mock_source = MagicMock() + mock_source.get_records.return_value = iter([ + {"id": 1, "name": "a"}, + {"id": 2, "name": "b"}, + ]) + + preview = engine.preview_stream(mock_source, "users", sample_size=5) + + assert preview.stream_name == "users" + assert preview.target_count == 100 + assert len(preview.sample_records) == 2 + + def test_preview_all_streams(self, engine): + """Test previewing multiple streams.""" + engine._spark.catalog.tableExists.return_value = False + + mock_source = MagicMock() + mock_source.get_records.return_value = iter([{"id": 1}]) + + result = engine.preview(mock_source, ["stream1", "stream2"]) + + assert len(result.streams) == 2 + assert result.streams[0].stream_name == "stream1" + assert result.streams[1].stream_name == "stream2" + diff --git a/tests/test_spark_streaming_writer.py b/tests/test_spark_streaming_writer.py new file mode 100644 index 0000000..c69b183 --- /dev/null +++ b/tests/test_spark_streaming_writer.py @@ -0,0 +1,173 @@ +""" +Unit tests for SparkStreamingWriter. +""" +from datetime import datetime +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte.writers.spark_streaming_writer import SparkStreamingWriter + + +class TestSparkStreamingWriter: + + @pytest.fixture + def writer(self): + """Create a SparkStreamingWriter with mocked Spark.""" + writer = SparkStreamingWriter( + catalog="main", + schema="test", + buffer_size_records=3, + buffer_size_mb=1, + ) + # Mock Spark session + writer._spark = MagicMock() + return writer + + def test_init_defaults(self): + """Test default initialization values.""" + writer = SparkStreamingWriter(catalog="main", schema="bronze") + + assert writer.catalog == "main" + assert writer.schema == "bronze" + assert writer.buffer_size_records == 50000 + assert writer.buffer_size_bytes == 100 * 1024 * 1024 # 100MB + + def test_get_table_name(self, writer): + """Test fully qualified table name generation.""" + assert writer.get_table_name("users") == "main.test.users" + assert writer.get_table_name("orders") == "main.test.orders" + + def test_transform_record(self, writer): + """Test that transform_record adds Airbyte metadata.""" + record = {"id": 1, "name": "test"} + transformed = writer._transform_record(record) + + assert "_airbyte_raw_id" in transformed + assert "_airbyte_extracted_at" in transformed + assert "_airbyte_data" in transformed + + # Verify UUID format + assert len(transformed["_airbyte_raw_id"]) == 36 + + # Verify datetime + assert isinstance(transformed["_airbyte_extracted_at"], datetime) + + # Verify JSON serialization + assert '"id": 1' in transformed["_airbyte_data"] + assert '"name": "test"' in transformed["_airbyte_data"] + + def test_write_record_buffers(self, writer): + """Test that write_record buffers records correctly.""" + writer.write_record("stream1", {"id": 1}) + writer.write_record("stream1", {"id": 2}) + + assert len(writer._buffers["stream1"]) == 2 + assert writer._buffer_counts["stream1"] == 2 + + def test_write_record_flushes_at_threshold(self, writer): + """Test that write_record flushes when record threshold is hit.""" + writer._write_micro_batch = MagicMock() + + # Write up to threshold (3 records) + writer.write_record("stream1", {"id": 1}) + writer.write_record("stream1", {"id": 2}) + assert writer._write_micro_batch.call_count == 0 + + # Third record should trigger flush + writer.write_record("stream1", {"id": 3}) + assert writer._write_micro_batch.call_count == 1 + + def test_write_micro_batch(self, writer): + """Test micro-batch write to Delta via createDataFrame.""" + # Setup buffer + writer._buffers["stream1"] = [ + {"_airbyte_raw_id": "1", "_airbyte_extracted_at": datetime.now(), "_airbyte_data": "{}"} + ] + writer._buffer_counts["stream1"] = 1 + writer._buffer_sizes["stream1"] = 100 + + # Mock Spark createDataFrame chain + mock_df = MagicMock() + mock_write = MagicMock() + mock_df.write = mock_write + mock_write.format.return_value = mock_write + mock_write.mode.return_value = mock_write + mock_write.option.return_value = mock_write + writer._spark.createDataFrame.return_value = mock_df + + writer._write_micro_batch("stream1") + + # Verify createDataFrame was called + writer._spark.createDataFrame.assert_called_once() + + # Verify write chain + mock_write.format.assert_called_with("delta") + mock_write.mode.assert_called_with("append") + mock_write.saveAsTable.assert_called_with("main.test.stream1") + + # Verify buffer was reset + assert writer._buffers["stream1"] == [] + assert writer._buffer_counts["stream1"] == 0 + assert writer._buffer_sizes["stream1"] == 0 + + def test_flush_stream_calls_write_micro_batch(self, writer): + """Test that flush_stream delegates to _write_micro_batch.""" + writer._write_micro_batch = MagicMock() + writer._buffers["stream1"] = [{"id": 1}] + + writer.flush_stream("stream1") + + writer._write_micro_batch.assert_called_once_with("stream1") + + def test_close_flushes_all_streams(self, writer): + """Test that close flushes all buffered streams.""" + writer.flush_stream = MagicMock() + writer._buffers["stream1"] = [{"id": 1}] + writer._buffers["stream2"] = [{"id": 2}] + + writer.close() + + assert writer.flush_stream.call_count == 2 + + def test_drop_table(self, writer): + """Test drop_table executes correct SQL.""" + writer.drop_table("users") + + writer._spark.sql.assert_called_with("DROP TABLE IF EXISTS main.test.users") + + def test_table_exists(self, writer): + """Test table_exists check.""" + writer._spark.catalog.tableExists.return_value = True + assert writer.table_exists("users") is True + + writer._spark.catalog.tableExists.return_value = False + assert writer.table_exists("orders") is False + + def test_get_table_schema(self, writer): + """Test getting table schema.""" + # Mock schema + mock_field1 = MagicMock() + mock_field1.name = "id" + mock_field1.dataType = "LongType" + + mock_field2 = MagicMock() + mock_field2.name = "name" + mock_field2.dataType = "StringType" + + mock_df = MagicMock() + mock_df.schema.fields = [mock_field1, mock_field2] + writer._spark.table.return_value = mock_df + writer._spark.catalog.tableExists.return_value = True + + schema = writer.get_table_schema("users") + + assert schema == {"id": "LongType", "name": "StringType"} + + def test_transform_record_handles_datetime(self, writer): + """Test that datetime objects in records are serialized.""" + record = {"id": 1, "created_at": datetime(2024, 1, 1, 12, 0, 0)} + transformed = writer._transform_record(record) + + # Should not raise, datetime converted to string via default=str + assert "2024-01-01" in transformed["_airbyte_data"] diff --git a/tests/test_streaming.py b/tests/test_streaming.py new file mode 100644 index 0000000..73ae4ff --- /dev/null +++ b/tests/test_streaming.py @@ -0,0 +1,75 @@ +""" +Unit tests for StreamingWriter. +""" +from unittest.mock import MagicMock, patch + +import pytest + +from brickbyte.writers import SQLStreamingWriter + + +class TestStreamingWriter: + + @pytest.fixture + def writer(self): + with patch("databricks.sql.connect") as mock_connect: + writer = SQLStreamingWriter( + catalog="main", + schema="test", + staging_volume="main.staging.vol", + server_hostname="test-host", + http_path="/sql", + access_token="token", + buffer_size_records=2 + ) + writer._connection = mock_connect.return_value + return writer + + def test_init_validation(self): + """Test validation of staging volume format.""" + with pytest.raises(ValueError): + SQLStreamingWriter( + catalog="main", + schema="test", + staging_volume="invalid_format", + server_hostname="h", + http_path="p", + access_token="t" + ) + + @patch("pyarrow.parquet.write_table") + @patch("os.remove") + @patch("os.makedirs") + def test_flush_logic(self, mock_makedirs, mock_remove, mock_pq_write, writer): + """Test that data is flushed correctly when threshold is met.""" + # Mock execution + writer._execute = MagicMock() + + # Write records + writer.write_record("stream1", {"id": 1}) + assert len(writer._buffers["stream1"]) == 1 + assert writer._execute.call_count == 0 # No flush yet + + # Write second record (hits threshold=2) + writer.write_record("stream1", {"id": 2}) + + # Should have flushed + assert len(writer._buffers["stream1"]) == 0 + assert writer._execute.call_count == 2 # CREATE + COPY INTO + mock_pq_write.assert_called_once() + mock_remove.assert_called_once() + + # Verify COPY INTO query + copy_call = writer._execute.call_args_list[1] + query = copy_call[0][0] + assert "COPY INTO" in query + assert "main.test.stream1" in query + + def test_close_flushes_remaining(self, writer): + """Test that close flushes remaining records.""" + writer.flush_stream = MagicMock() + writer._buffers["s1"] = [{"id": 1}] + + writer.close() + + writer.flush_stream.assert_called_with("s1")