diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/__init__.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/__init__.py new file mode 100644 index 00000000000..66f6de8cb2b --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/conftest.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/conftest.py new file mode 100644 index 00000000000..a80da4f57e0 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/conftest.py @@ -0,0 +1,53 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +import os +import sys +from pathlib import Path + +from pytest import fixture + +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource +from airbyte_cdk.test.catalog_builder import CatalogBuilder +from airbyte_cdk.test.state_builder import StateBuilder + + +pytest_plugins = ["airbyte_cdk.test.utils.manifest_only_fixtures"] + + +@fixture(autouse=True) +def mock_sleep(mocker): + mocker.patch("time.sleep") + + +def _get_manifest_path() -> Path: + source_declarative_manifest_path = Path("/airbyte/integration_code/source_declarative_manifest") + if source_declarative_manifest_path.exists(): + return source_declarative_manifest_path + return Path(__file__).parent.parent + + +_SOURCE_FOLDER_PATH = _get_manifest_path() +_YAML_FILE_PATH = _SOURCE_FOLDER_PATH / "manifest.yaml" + +sys.path.append(str(_SOURCE_FOLDER_PATH)) + + +def get_source(config, state=None) -> YamlDeclarativeSource: + catalog = CatalogBuilder().build() + state = StateBuilder().build() if not state else state + return YamlDeclarativeSource(path_to_yaml=str(_YAML_FILE_PATH), catalog=catalog, config=config, state=state) + + +def find_stream(stream_name, config, state=None): + state = StateBuilder().build() if not state else state + streams = get_source(config, state).streams(config=config) + for stream in streams: + if stream.name == stream_name: + return stream + raise ValueError(f"Stream {stream_name} not found") + + +SNAPCHAT_API_URL = "https://adsapi.snapchat.com/v1" +OAUTH_TOKEN_URL = "https://accounts.snapchat.com/login/oauth2/access_token" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/__init__.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/__init__.py new file mode 100644 index 00000000000..66f6de8cb2b --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/config.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/config.py new file mode 100644 index 00000000000..843aef2f33e --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/config.py @@ -0,0 +1,79 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from __future__ import annotations + +from typing import Any, List, MutableMapping, Optional + + +CLIENT_ID = "test_client_id" +CLIENT_SECRET = "test_client_secret" +REFRESH_TOKEN = "test_refresh_token" +ACCESS_TOKEN = "test_access_token" + +ORGANIZATION_ID = "test_org_123" +AD_ACCOUNT_ID = "test_adaccount_456" +CAMPAIGN_ID = "test_campaign_789" +ADSQUAD_ID = "test_adsquad_012" +AD_ID = "test_ad_345" + +START_DATE = "2024-01-01" +END_DATE = "2024-01-31" + + +class ConfigBuilder: + def __init__(self) -> None: + self._config: MutableMapping[str, Any] = { + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "refresh_token": REFRESH_TOKEN, + "start_date": START_DATE, + "end_date": END_DATE, + "action_report_time": "conversion", + "swipe_up_attribution_window": "28_DAY", + "view_attribution_window": "1_DAY", + } + + def with_client_id(self, client_id: str) -> "ConfigBuilder": + self._config["client_id"] = client_id + return self + + def with_client_secret(self, client_secret: str) -> "ConfigBuilder": + self._config["client_secret"] = client_secret + return self + + def with_refresh_token(self, refresh_token: str) -> "ConfigBuilder": + self._config["refresh_token"] = refresh_token + return self + + def with_start_date(self, start_date: str) -> "ConfigBuilder": + self._config["start_date"] = start_date + return self + + def with_end_date(self, end_date: str) -> "ConfigBuilder": + self._config["end_date"] = end_date + return self + + def with_organization_ids(self, organization_ids: List[str]) -> "ConfigBuilder": + self._config["organization_ids"] = organization_ids + return self + + def with_ad_account_ids(self, ad_account_ids: List[str]) -> "ConfigBuilder": + self._config["ad_account_ids"] = ad_account_ids + return self + + def with_action_report_time(self, action_report_time: str) -> "ConfigBuilder": + self._config["action_report_time"] = action_report_time + return self + + def with_swipe_up_attribution_window(self, window: str) -> "ConfigBuilder": + self._config["swipe_up_attribution_window"] = window + return self + + def with_view_attribution_window(self, window: str) -> "ConfigBuilder": + self._config["view_attribution_window"] = window + return self + + def build(self) -> MutableMapping[str, Any]: + return self._config diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/request_builder.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/request_builder.py new file mode 100644 index 00000000000..7a82030e876 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/request_builder.py @@ -0,0 +1,153 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Union + +from airbyte_cdk.test.mock_http.request import ANY_QUERY_PARAMS, HttpRequest + +from .config import ( + AD_ACCOUNT_ID, + AD_ID, + ADSQUAD_ID, + CAMPAIGN_ID, + ORGANIZATION_ID, +) + + +SNAPCHAT_API_URL = "https://adsapi.snapchat.com/v1" +OAUTH_TOKEN_URL = "https://accounts.snapchat.com/login/oauth2/access_token" + + +class OAuthRequestBuilder: + @classmethod + def oauth_endpoint( + cls, + client_id: str = "test_client_id", + client_secret: str = "test_client_secret", + refresh_token: str = "test_refresh_token", + ) -> "OAuthRequestBuilder": + return cls(client_id, client_secret, refresh_token) + + def __init__( + self, + client_id: str = "test_client_id", + client_secret: str = "test_client_secret", + refresh_token: str = "test_refresh_token", + ) -> None: + self._body = f"grant_type=refresh_token&client_id={client_id}&client_secret={client_secret}&refresh_token={refresh_token}" + + def build(self) -> HttpRequest: + return HttpRequest( + url=OAUTH_TOKEN_URL, + body=self._body, + ) + + +class RequestBuilder: + @classmethod + def organizations_endpoint(cls, organization_id: str = "me") -> "RequestBuilder": + if organization_id == "me": + return cls(resource="me/organizations") + return cls(resource=f"organizations/{organization_id}") + + @classmethod + def adaccounts_endpoint(cls, organization_id: str = ORGANIZATION_ID) -> "RequestBuilder": + return cls(resource=f"organizations/{organization_id}/adaccounts") + + @classmethod + def adaccounts_by_id_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}") + + @classmethod + def creatives_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/creatives") + + @classmethod + def ads_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/ads") + + @classmethod + def adsquads_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/adsquads") + + @classmethod + def segments_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/segments") + + @classmethod + def media_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/media") + + @classmethod + def campaigns_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/campaigns") + + @classmethod + def adaccounts_stats_endpoint(cls, ad_account_id: str = AD_ACCOUNT_ID) -> "RequestBuilder": + return cls(resource=f"adaccounts/{ad_account_id}/stats") + + @classmethod + def ads_stats_endpoint(cls, ad_id: str = AD_ID) -> "RequestBuilder": + return cls(resource=f"ads/{ad_id}/stats") + + @classmethod + def adsquads_stats_endpoint(cls, adsquad_id: str = ADSQUAD_ID) -> "RequestBuilder": + return cls(resource=f"adsquads/{adsquad_id}/stats") + + @classmethod + def campaigns_stats_endpoint(cls, campaign_id: str = CAMPAIGN_ID) -> "RequestBuilder": + return cls(resource=f"campaigns/{campaign_id}/stats") + + def __init__(self, resource: str = "") -> None: + self._resource = resource + self._query_params: Dict[str, Any] = {} + self._body = None + + def with_query_param(self, key: str, value: Any) -> "RequestBuilder": + self._query_params[key] = value + return self + + def with_granularity(self, granularity: str) -> "RequestBuilder": + self._query_params["granularity"] = granularity + return self + + def with_fields(self, fields: str) -> "RequestBuilder": + self._query_params["fields"] = fields + return self + + def with_start_time(self, start_time: str) -> "RequestBuilder": + self._query_params["start_time"] = start_time + return self + + def with_end_time(self, end_time: str) -> "RequestBuilder": + self._query_params["end_time"] = end_time + return self + + def with_action_report_time(self, action_report_time: str) -> "RequestBuilder": + self._query_params["action_report_time"] = action_report_time + return self + + def with_view_attribution_window(self, window: str) -> "RequestBuilder": + self._query_params["view_attribution_window"] = window + return self + + def with_swipe_up_attribution_window(self, window: str) -> "RequestBuilder": + self._query_params["swipe_up_attribution_window"] = window + return self + + def with_any_query_params(self) -> "RequestBuilder": + self._any_query_params = True + return self + + def build(self) -> HttpRequest: + query_params = ( + ANY_QUERY_PARAMS if getattr(self, "_any_query_params", False) else (self._query_params if self._query_params else None) + ) + return HttpRequest( + url=f"{SNAPCHAT_API_URL}/{self._resource}", + query_params=query_params, + body=self._body, + ) diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/response_builder.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/response_builder.py new file mode 100644 index 00000000000..83a3ada0a14 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/response_builder.py @@ -0,0 +1,349 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +import copy +import json +from http import HTTPStatus +from typing import Any, List, Optional + +from airbyte_cdk.test.mock_http import HttpResponse +from airbyte_cdk.test.mock_http.response_builder import find_template + +from .config import ( + AD_ACCOUNT_ID, + AD_ID, + ADSQUAD_ID, + CAMPAIGN_ID, + ORGANIZATION_ID, +) + + +def _set_nested_value(obj: Any, key: str, value: Any) -> bool: + """Recursively set a value in a nested structure.""" + if isinstance(obj, dict): + if key in obj: + obj[key] = value + return True + for v in obj.values(): + if _set_nested_value(v, key, value): + return True + elif isinstance(obj, list): + for item in obj: + if _set_nested_value(item, key, value): + return True + return False + + +def create_response( + resource_name: str, + status_code: int = 200, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create an HttpResponse from a JSON template file. + + Args: + resource_name: Name of the JSON template file (without .json extension) + status_code: HTTP status code for the response + has_next: Whether to include pagination next_link + next_link: The URL for the next page + + Returns: + HttpResponse with the template body + """ + body = copy.deepcopy(find_template(resource_name, __file__)) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=status_code) + + +def create_response_with_id( + resource_name: str, + record_id: str, + status_code: int = 200, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create an HttpResponse from a JSON template with a specific record ID.""" + body = copy.deepcopy(find_template(resource_name, __file__)) + _set_nested_value(body, "id", record_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=status_code) + + +def create_empty_response(resource_name: str) -> HttpResponse: + """Create an empty response for a given resource.""" + body = copy.deepcopy(find_template(resource_name, __file__)) + + for key in body: + if isinstance(body[key], list) and key not in ["request_status", "request_id"]: + body[key] = [] + break + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def create_error_response(status_code: HTTPStatus = HTTPStatus.UNAUTHORIZED) -> HttpResponse: + """Create an error response from a JSON template.""" + error_template_map = { + HTTPStatus.UNAUTHORIZED: "error_401", + HTTPStatus.TOO_MANY_REQUESTS: "error_429", + } + + template_name = error_template_map.get(status_code) + if template_name: + body = copy.deepcopy(find_template(template_name, __file__)) + else: + body = {"request_status": "ERROR", "request_id": "test_request_id", "msg": f"Error {status_code.value}"} + + return HttpResponse(body=json.dumps(body), status_code=status_code.value) + + +def create_oauth_response() -> HttpResponse: + """Create an OAuth token response from JSON template.""" + body = copy.deepcopy(find_template("oauth_token", __file__)) + return HttpResponse(body=json.dumps(body), status_code=200) + + +def create_stats_response( + resource_name: str, + entity_id: str, + granularity: str = "HOUR", + status_code: int = 200, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a stats response with specific entity ID and granularity.""" + body = copy.deepcopy(find_template(resource_name, __file__)) + _set_nested_value(body, "id", entity_id) + _set_nested_value(body, "granularity", granularity) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=status_code) + + +def create_multiple_records_response( + resource_name: str, + record_ids: List[str], + status_code: int = 200, +) -> HttpResponse: + """Create a response with multiple records for testing substreams with multiple parents.""" + template = find_template(resource_name, __file__) + body = copy.deepcopy(template) + + data_key = None + record_template = None + for key in body: + if isinstance(body[key], list) and key not in ["request_status", "request_id"]: + data_key = key + if body[key]: + record_template = copy.deepcopy(body[key][0]) + break + + if data_key and record_template: + body[data_key] = [] + for record_id in record_ids: + record = copy.deepcopy(record_template) + _set_nested_value(record, "id", record_id) + body[data_key].append(record) + + return HttpResponse(body=json.dumps(body), status_code=status_code) + + +# Legacy helper functions that wrap the new template-based functions +# These maintain backward compatibility with existing tests + + +def oauth_response() -> HttpResponse: + """Create an OAuth token response.""" + return create_oauth_response() + + +def organizations_response( + organization_id: str = ORGANIZATION_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create an organizations response using JSON template.""" + return create_response_with_id("organizations", organization_id, has_next=has_next, next_link=next_link) + + +def adaccounts_response( + ad_account_id: str = AD_ACCOUNT_ID, + organization_id: str = ORGANIZATION_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create an adaccounts response using JSON template.""" + body = copy.deepcopy(find_template("adaccounts", __file__)) + _set_nested_value(body, "id", ad_account_id) + _set_nested_value(body, "organization_id", organization_id) + _set_nested_value(body, "advertiser_organization_id", organization_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def adaccounts_response_multiple( + ad_account_ids: List[str], + organization_id: str = ORGANIZATION_ID, +) -> HttpResponse: + """Create response with multiple ad accounts for testing substreams with multiple parents.""" + return create_multiple_records_response("adaccounts", ad_account_ids) + + +def organizations_response_multiple( + organization_ids: List[str], +) -> HttpResponse: + """Create response with multiple organizations for testing substreams with multiple parents.""" + return create_multiple_records_response("organizations", organization_ids) + + +def adsquads_response_multiple( + adsquad_ids: List[str], +) -> HttpResponse: + """Create response with multiple adsquads for testing substreams with multiple parents.""" + return create_multiple_records_response("adsquads", adsquad_ids) + + +def creatives_response( + creative_id: str = "test_creative_123", + ad_account_id: str = AD_ACCOUNT_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a creatives response using JSON template.""" + body = copy.deepcopy(find_template("creatives", __file__)) + _set_nested_value(body, "id", creative_id) + _set_nested_value(body, "ad_account_id", ad_account_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def ads_response( + ad_id: str = AD_ID, + ad_account_id: str = AD_ACCOUNT_ID, + adsquad_id: str = ADSQUAD_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create an ads response using JSON template.""" + body = copy.deepcopy(find_template("ads", __file__)) + _set_nested_value(body, "id", ad_id) + _set_nested_value(body, "ad_squad_id", adsquad_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def adsquads_response( + adsquad_id: str = ADSQUAD_ID, + ad_account_id: str = AD_ACCOUNT_ID, + campaign_id: str = CAMPAIGN_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create an adsquads response using JSON template.""" + body = copy.deepcopy(find_template("adsquads", __file__)) + _set_nested_value(body, "id", adsquad_id) + _set_nested_value(body, "campaign_id", campaign_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def segments_response( + segment_id: str = "test_segment_123", + ad_account_id: str = AD_ACCOUNT_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a segments response using JSON template.""" + body = copy.deepcopy(find_template("segments", __file__)) + _set_nested_value(body, "id", segment_id) + _set_nested_value(body, "ad_account_id", ad_account_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def media_response( + media_id: str = "test_media_123", + ad_account_id: str = AD_ACCOUNT_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a media response using JSON template.""" + body = copy.deepcopy(find_template("media", __file__)) + _set_nested_value(body, "id", media_id) + _set_nested_value(body, "ad_account_id", ad_account_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def campaigns_response( + campaign_id: str = CAMPAIGN_ID, + ad_account_id: str = AD_ACCOUNT_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a campaigns response using JSON template.""" + body = copy.deepcopy(find_template("campaigns", __file__)) + _set_nested_value(body, "id", campaign_id) + _set_nested_value(body, "ad_account_id", ad_account_id) + + if has_next and next_link: + body["paging"] = {"next_link": next_link} + + return HttpResponse(body=json.dumps(body), status_code=200) + + +def stats_timeseries_response( + entity_id: str = AD_ACCOUNT_ID, + granularity: str = "HOUR", + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a stats timeseries response using JSON template.""" + return create_stats_response("stats_timeseries", entity_id, granularity, has_next=has_next, next_link=next_link) + + +def stats_lifetime_response( + entity_id: str = AD_ACCOUNT_ID, + has_next: bool = False, + next_link: Optional[str] = None, +) -> HttpResponse: + """Create a stats lifetime response using JSON template.""" + return create_stats_response("stats_lifetime", entity_id, "LIFETIME", has_next=has_next, next_link=next_link) + + +def error_response(status_code: HTTPStatus = HTTPStatus.UNAUTHORIZED) -> HttpResponse: + """Create an error response using JSON template.""" + return create_error_response(status_code) + + +def empty_response(stream_key: str = "organizations") -> HttpResponse: + """Create an empty response for a given stream using JSON template.""" + return create_empty_response(stream_key) diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adaccounts.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adaccounts.py new file mode 100644 index 00000000000..2e017476b4c --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adaccounts.py @@ -0,0 +1,248 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.mock_http.request import HttpRequest +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + error_response, + oauth_response, + organizations_response, + organizations_response_multiple, +) +from .utils import config, read_output + + +_STREAM_NAME = "adaccounts" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestAdaccounts(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == AD_ACCOUNT_ID + + @HttpMocker() + def test_read_records_with_pagination(self, http_mocker: HttpMocker) -> None: + """Test pagination for adaccounts stream. + + NOTE: This test covers pagination for ALL streams in this connector + because they all use the same DefaultPaginator with identical + CursorPagination strategy (cursor_value from response.paging.next_link, + stop_condition when next_link is empty). Writing separate pagination + tests for each stream would be redundant. + """ + next_link = "https://adsapi.snapchat.com/v1/organizations/test_org_123/adaccounts?cursor=page2" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id="adaccount_1", has_next=True, next_link=next_link), + ) + http_mocker.get( + HttpRequest(url=next_link), + adaccounts_response(ad_account_id="adaccount_2", has_next=False), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 2 + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == AD_ACCOUNT_ID + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestAdaccountsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The adaccounts stream uses SubstreamPartitionRouter with organizations as parent. + This test verifies that adaccounts are fetched for each parent organization. + """ + org_1 = "org_001" + org_2 = "org_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response_multiple([org_1, org_2]), + ) + # Mock adaccounts endpoint for each parent organization + http_mocker.get( + RequestBuilder.adaccounts_endpoint(org_1).build(), + adaccounts_response(ad_account_id="adaccount_from_org_1", organization_id=org_1), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(org_2).build(), + adaccounts_response(ad_account_id="adaccount_from_org_2", organization_id=org_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent organizations are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "adaccount_from_org_1" in record_ids + assert "adaccount_from_org_2" in record_ids + + +class TestAdaccountsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state. + + This test validates: + - Connector accepts state from previous sync + - State is passed to both get_source() and read() + - Records are returned + - State advances to latest record's cursor value + """ + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adaccounts_stats.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adaccounts_stats.py new file mode 100644 index 00000000000..2ec25b89ad2 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adaccounts_stats.py @@ -0,0 +1,272 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker + +from .config import AD_ACCOUNT_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + error_response, + oauth_response, + organizations_response, + stats_lifetime_response, + stats_timeseries_response, +) +from .utils import config, read_output + + +def _read( + config_builder: ConfigBuilder, + stream_name: str, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=stream_name, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +def _setup_parent_mocks(http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + + +def _setup_parent_mocks_multiple_adaccounts(http_mocker: HttpMocker, ad_account_ids: List[str]) -> None: + """Setup parent mocks with multiple ad accounts for testing substreams.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple(ad_account_ids=ad_account_ids, organization_id=ORGANIZATION_ID), + ) + + +class TestAdaccountsStatsHourly(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ACCOUNT_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_hourly") + + # Enhanced assertions + assert len(output.records) == 5 # 5 weekly time slices (Jan 1-31 with step: P1W) + record = output.records[0].record.data + assert record.get("id") == AD_ACCOUNT_ID, f"Expected id={AD_ACCOUNT_ID}, got {record.get('id')}" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + _setup_parent_mocks(http_mocker) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + [ + error_response(HTTPStatus.FORBIDDEN), + stats_timeseries_response(entity_id=AD_ACCOUNT_ID, granularity="HOUR"), + ], + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + + +class TestAdaccountsStatsDaily(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ACCOUNT_ID, granularity="DAY"), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_daily") + + assert len(output.records) == 1 # Daily: step P1M = 1 monthly slice + record = output.records[0].record.data + assert record.get("id") == AD_ACCOUNT_ID, f"Expected id={AD_ACCOUNT_ID}, got {record.get('id')}" + + +class TestAdaccountsStatsLifetime(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + stats_lifetime_response(entity_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_lifetime") + + assert len(output.records) == 1 # Lifetime: no step + record = output.records[0].record.data + assert record.get("id") == AD_ACCOUNT_ID, f"Expected id={AD_ACCOUNT_ID}, got {record.get('id')}" + + +class TestAdaccountsStatsTransformations(TestCase): + @HttpMocker() + def test_transformations_add_fields(self, http_mocker: HttpMocker) -> None: + """Test that AddFields transformations are applied correctly. + + The manifest defines these transformations for adaccounts_stats_hourly: + - AddFields: id (from stream_slice['id']) + - AddFields: type = AD_ACCOUNT + - AddFields: granularity = HOUR + - AddFields: spend (from record.get('stats', {}).get('spend')) + - RemoveFields: stats + """ + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ACCOUNT_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + record = output.records[0].record.data + # Verify AddFields transformations + assert record.get("id") == AD_ACCOUNT_ID + assert record.get("type") == "AD_ACCOUNT" + assert record.get("granularity") == "HOUR" + # Verify spend field is extracted from stats + assert "spend" in record + # Verify RemoveFields transformation - stats should be removed + assert "stats" not in record + + +class TestAdaccountsStatsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The adaccounts_stats streams use SubstreamPartitionRouter with adaccounts as parent. + This test verifies that stats are fetched for each parent ad account. + """ + ad_account_1 = "adaccount_001" + ad_account_2 = "adaccount_002" + + _setup_parent_mocks_multiple_adaccounts(http_mocker, [ad_account_1, ad_account_2]) + + # Mock stats endpoint for each parent ad account + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(ad_account_1).with_any_query_params().build(), + stats_timeseries_response(entity_id=ad_account_1, granularity="HOUR"), + ) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(ad_account_2).with_any_query_params().build(), + stats_timeseries_response(entity_id=ad_account_2, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_hourly") + + # Verify records from both parent ad accounts are returned + assert len(output.records) == 10 # 2 parents × 5 weekly time slices = 10 records + record_ids = [r.record.data.get("id") for r in output.records] + assert ad_account_1 in record_ids + assert ad_account_2 in record_ids + + +class TestAdaccountsStatsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ACCOUNT_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_hourly", sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 5 # 5 weekly time slices + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state for stats streams.""" + from airbyte_cdk.test.state_builder import StateBuilder + + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state("adaccounts_stats_hourly", {"start_time": previous_state_date}).build() + + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adaccounts_stats_endpoint(AD_ACCOUNT_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ACCOUNT_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adaccounts_stats_hourly", sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 3 # 3 remaining weekly time slices after state date (Jan 15-31) + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_ads.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_ads.py new file mode 100644 index 00000000000..1c2404aef14 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_ads.py @@ -0,0 +1,230 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, AD_ID, ADSQUAD_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + ads_response, + error_response, + oauth_response, + organizations_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "ads" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestAds(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.ads_endpoint(AD_ACCOUNT_ID).build(), + ads_response(ad_id=AD_ID, ad_account_id=AD_ACCOUNT_ID, adsquad_id=ADSQUAD_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == AD_ID + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.ads_endpoint(AD_ACCOUNT_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + ads_response(ad_id=AD_ID, ad_account_id=AD_ACCOUNT_ID, adsquad_id=ADSQUAD_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == AD_ID + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestAdsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The ads stream uses SubstreamPartitionRouter with adaccounts as parent. + This test verifies that ads are fetched for each parent adaccount. + """ + adaccount_1 = "adaccount_001" + adaccount_2 = "adaccount_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple([adaccount_1, adaccount_2]), + ) + # Mock ads endpoint for each parent adaccount + http_mocker.get( + RequestBuilder.ads_endpoint(adaccount_1).build(), + ads_response(ad_id="ad_from_adaccount_1", ad_account_id=adaccount_1), + ) + http_mocker.get( + RequestBuilder.ads_endpoint(adaccount_2).build(), + ads_response(ad_id="ad_from_adaccount_2", ad_account_id=adaccount_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent adaccounts are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "ad_from_adaccount_1" in record_ids + assert "ad_from_adaccount_2" in record_ids + + +class TestAdsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.ads_endpoint(AD_ACCOUNT_ID).build(), + ads_response(ad_id=AD_ID, ad_account_id=AD_ACCOUNT_ID, adsquad_id=ADSQUAD_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.ads_endpoint(AD_ACCOUNT_ID).build(), + ads_response(ad_id=AD_ID, ad_account_id=AD_ACCOUNT_ID, adsquad_id=ADSQUAD_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_ads_stats.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_ads_stats.py new file mode 100644 index 00000000000..ec5ffdf9a65 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_ads_stats.py @@ -0,0 +1,280 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, AD_ID, ADSQUAD_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + ads_response, + create_multiple_records_response, + error_response, + oauth_response, + organizations_response, + stats_lifetime_response, + stats_timeseries_response, +) +from .utils import config, read_output + + +def _read( + config_builder: ConfigBuilder, + stream_name: str, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=stream_name, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +def _setup_parent_mocks(http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.ads_endpoint(AD_ACCOUNT_ID).build(), + ads_response(ad_id=AD_ID, ad_account_id=AD_ACCOUNT_ID, adsquad_id=ADSQUAD_ID), + ) + + +def _setup_parent_mocks_multiple_ads(http_mocker: HttpMocker, ad_ids: List[str]) -> None: + """Setup parent mocks with multiple ads for testing substreams.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.ads_endpoint(AD_ACCOUNT_ID).build(), + create_multiple_records_response("ads", ad_ids), + ) + + +class TestAdsStatsHourly(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_hourly") + + # Enhanced assertions + assert len(output.records) == 5 # 5 weekly time slices (Jan 1-31 with step: P1W) + record = output.records[0].record.data + assert record.get("id") == AD_ID, f"Expected id={AD_ID}, got {record.get('id')}" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + _setup_parent_mocks(http_mocker) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + [ + error_response(HTTPStatus.FORBIDDEN), + stats_timeseries_response(entity_id=AD_ID, granularity="HOUR"), + ], + ) + + output = _read(config_builder=config(), stream_name="ads_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + + +class TestAdsStatsDaily(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ID, granularity="DAY"), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_daily") + + assert len(output.records) == 1 # Daily: step P1M = 1 monthly slice + record = output.records[0].record.data + assert record.get("id") == AD_ID, f"Expected id={AD_ID}, got {record.get('id')}" + + +class TestAdsStatsLifetime(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + stats_lifetime_response(entity_id=AD_ID), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_lifetime") + + assert len(output.records) == 1 # Lifetime: no step + record = output.records[0].record.data + assert record.get("id") == AD_ID, f"Expected id={AD_ID}, got {record.get('id')}" + + +class TestAdsStatsTransformations(TestCase): + @HttpMocker() + def test_transformations_add_fields(self, http_mocker: HttpMocker) -> None: + """Test that AddFields transformations are applied correctly. + + The manifest defines these transformations for ads_stats_hourly: + - AddFields: id (from stream_slice['id']) + - AddFields: type = AD + - AddFields: granularity = HOUR + - AddFields: spend (from record.get('stats', {}).get('spend')) + - RemoveFields: stats + """ + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + record = output.records[0].record.data + # Verify AddFields transformations + assert record.get("id") == AD_ID + assert record.get("type") == "AD" + assert record.get("granularity") == "HOUR" + # Verify spend field is extracted from stats + assert "spend" in record + # Verify RemoveFields transformation - stats should be removed + assert "stats" not in record + + +class TestAdsStatsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The ads_stats streams use SubstreamPartitionRouter with ads as parent. + This test verifies that stats are fetched for each parent ad. + """ + ad_1 = "ad_001" + ad_2 = "ad_002" + + _setup_parent_mocks_multiple_ads(http_mocker, [ad_1, ad_2]) + + # Mock stats endpoint for each parent ad + http_mocker.get( + RequestBuilder.ads_stats_endpoint(ad_1).with_any_query_params().build(), + stats_timeseries_response(entity_id=ad_1, granularity="HOUR"), + ) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(ad_2).with_any_query_params().build(), + stats_timeseries_response(entity_id=ad_2, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_hourly") + + # Verify records from both parent ads are returned + assert len(output.records) == 10 # 2 parents × 5 weekly time slices = 10 records + record_ids = [r.record.data.get("id") for r in output.records] + assert ad_1 in record_ids + assert ad_2 in record_ids + + +class TestAdsStatsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_hourly", sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 5 # 5 weekly time slices + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state for stats streams.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state("ads_stats_hourly", {"start_time": previous_state_date}).build() + + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.ads_stats_endpoint(AD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=AD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="ads_stats_hourly", sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 3 # 3 remaining weekly time slices after state date (Jan 15-31) + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adsquads.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adsquads.py new file mode 100644 index 00000000000..e490fb311c2 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adsquads.py @@ -0,0 +1,230 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, ADSQUAD_ID, CAMPAIGN_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + adsquads_response, + error_response, + oauth_response, + organizations_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "adsquads" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestAdsquads(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adsquads_endpoint(AD_ACCOUNT_ID).build(), + adsquads_response(adsquad_id=ADSQUAD_ID, ad_account_id=AD_ACCOUNT_ID, campaign_id=CAMPAIGN_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == ADSQUAD_ID + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.adsquads_endpoint(AD_ACCOUNT_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + adsquads_response(adsquad_id=ADSQUAD_ID, ad_account_id=AD_ACCOUNT_ID, campaign_id=CAMPAIGN_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == ADSQUAD_ID + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestAdsquadsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The adsquads stream uses SubstreamPartitionRouter with adaccounts as parent. + This test verifies that adsquads are fetched for each parent adaccount. + """ + adaccount_1 = "adaccount_001" + adaccount_2 = "adaccount_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple([adaccount_1, adaccount_2]), + ) + # Mock adsquads endpoint for each parent adaccount + http_mocker.get( + RequestBuilder.adsquads_endpoint(adaccount_1).build(), + adsquads_response(adsquad_id="adsquad_from_adaccount_1", ad_account_id=adaccount_1), + ) + http_mocker.get( + RequestBuilder.adsquads_endpoint(adaccount_2).build(), + adsquads_response(adsquad_id="adsquad_from_adaccount_2", ad_account_id=adaccount_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent adaccounts are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "adsquad_from_adaccount_1" in record_ids + assert "adsquad_from_adaccount_2" in record_ids + + +class TestAdsquadsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adsquads_endpoint(AD_ACCOUNT_ID).build(), + adsquads_response(adsquad_id=ADSQUAD_ID, ad_account_id=AD_ACCOUNT_ID, campaign_id=CAMPAIGN_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adsquads_endpoint(AD_ACCOUNT_ID).build(), + adsquads_response(adsquad_id=ADSQUAD_ID, ad_account_id=AD_ACCOUNT_ID, campaign_id=CAMPAIGN_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adsquads_stats.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adsquads_stats.py new file mode 100644 index 00000000000..583ad21b14a --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_adsquads_stats.py @@ -0,0 +1,280 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, ADSQUAD_ID, CAMPAIGN_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adsquads_response, + create_multiple_records_response, + error_response, + oauth_response, + organizations_response, + stats_lifetime_response, + stats_timeseries_response, +) +from .utils import config, read_output + + +def _read( + config_builder: ConfigBuilder, + stream_name: str, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=stream_name, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +def _setup_parent_mocks(http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adsquads_endpoint(AD_ACCOUNT_ID).build(), + adsquads_response(adsquad_id=ADSQUAD_ID, ad_account_id=AD_ACCOUNT_ID, campaign_id=CAMPAIGN_ID), + ) + + +def _setup_parent_mocks_multiple_adsquads(http_mocker: HttpMocker, adsquad_ids: List[str]) -> None: + """Setup parent mocks with multiple adsquads for testing substreams.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adsquads_endpoint(AD_ACCOUNT_ID).build(), + create_multiple_records_response("adsquads", adsquad_ids), + ) + + +class TestAdsquadsStatsHourly(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=ADSQUAD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_hourly") + + # Enhanced assertions + assert len(output.records) == 5 # 5 weekly time slices (Jan 1-31 with step: P1W) + record = output.records[0].record.data + assert record.get("id") == ADSQUAD_ID, f"Expected id={ADSQUAD_ID}, got {record.get('id')}" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + _setup_parent_mocks(http_mocker) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + [ + error_response(HTTPStatus.FORBIDDEN), + stats_timeseries_response(entity_id=ADSQUAD_ID, granularity="HOUR"), + ], + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + + +class TestAdsquadsStatsDaily(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=ADSQUAD_ID, granularity="DAY"), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_daily") + + assert len(output.records) == 1 # Daily: step P1M = 1 monthly slice + record = output.records[0].record.data + assert record.get("id") == ADSQUAD_ID, f"Expected id={ADSQUAD_ID}, got {record.get('id')}" + + +class TestAdsquadsStatsLifetime(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + stats_lifetime_response(entity_id=ADSQUAD_ID), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_lifetime") + + assert len(output.records) == 1 # Lifetime: no step + record = output.records[0].record.data + assert record.get("id") == ADSQUAD_ID, f"Expected id={ADSQUAD_ID}, got {record.get('id')}" + + +class TestAdsquadsStatsTransformations(TestCase): + @HttpMocker() + def test_transformations_add_fields(self, http_mocker: HttpMocker) -> None: + """Test that AddFields transformations are applied correctly. + + The manifest defines these transformations for adsquads_stats_hourly: + - AddFields: id (from stream_slice['id']) + - AddFields: type = AD_SQUAD + - AddFields: granularity = HOUR + - AddFields: spend (from record.get('stats', {}).get('spend')) + - RemoveFields: stats + """ + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=ADSQUAD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + record = output.records[0].record.data + # Verify AddFields transformations + assert record.get("id") == ADSQUAD_ID + assert record.get("type") == "AD_SQUAD" + assert record.get("granularity") == "HOUR" + # Verify spend field is extracted from stats + assert "spend" in record + # Verify RemoveFields transformation - stats should be removed + assert "stats" not in record + + +class TestAdsquadsStatsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The adsquads_stats streams use SubstreamPartitionRouter with adsquads as parent. + This test verifies that stats are fetched for each parent adsquad. + """ + adsquad_1 = "adsquad_001" + adsquad_2 = "adsquad_002" + + _setup_parent_mocks_multiple_adsquads(http_mocker, [adsquad_1, adsquad_2]) + + # Mock stats endpoint for each parent adsquad + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(adsquad_1).with_any_query_params().build(), + stats_timeseries_response(entity_id=adsquad_1, granularity="HOUR"), + ) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(adsquad_2).with_any_query_params().build(), + stats_timeseries_response(entity_id=adsquad_2, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_hourly") + + # Verify records from both parent adsquads are returned + assert len(output.records) == 10 # 2 parents × 5 weekly time slices = 10 records + record_ids = [r.record.data.get("id") for r in output.records] + assert adsquad_1 in record_ids + assert adsquad_2 in record_ids + + +class TestAdsquadsStatsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=ADSQUAD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_hourly", sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 5 # 5 weekly time slices + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state for stats streams.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state("adsquads_stats_hourly", {"start_time": previous_state_date}).build() + + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.adsquads_stats_endpoint(ADSQUAD_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=ADSQUAD_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="adsquads_stats_hourly", sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 3 # 3 remaining weekly time slices after state date (Jan 15-31) + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_campaigns.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_campaigns.py new file mode 100644 index 00000000000..5c1277feabf --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_campaigns.py @@ -0,0 +1,230 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, CAMPAIGN_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + campaigns_response, + error_response, + oauth_response, + organizations_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "campaigns" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestCampaigns(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.campaigns_endpoint(AD_ACCOUNT_ID).build(), + campaigns_response(campaign_id=CAMPAIGN_ID, ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == CAMPAIGN_ID + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.campaigns_endpoint(AD_ACCOUNT_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + campaigns_response(campaign_id=CAMPAIGN_ID, ad_account_id=AD_ACCOUNT_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == CAMPAIGN_ID + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestCampaignsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The campaigns stream uses SubstreamPartitionRouter with adaccounts as parent. + This test verifies that campaigns are fetched for each parent adaccount. + """ + adaccount_1 = "adaccount_001" + adaccount_2 = "adaccount_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple([adaccount_1, adaccount_2]), + ) + # Mock campaigns endpoint for each parent adaccount + http_mocker.get( + RequestBuilder.campaigns_endpoint(adaccount_1).build(), + campaigns_response(campaign_id="campaign_from_adaccount_1", ad_account_id=adaccount_1), + ) + http_mocker.get( + RequestBuilder.campaigns_endpoint(adaccount_2).build(), + campaigns_response(campaign_id="campaign_from_adaccount_2", ad_account_id=adaccount_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent adaccounts are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "campaign_from_adaccount_1" in record_ids + assert "campaign_from_adaccount_2" in record_ids + + +class TestCampaignsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.campaigns_endpoint(AD_ACCOUNT_ID).build(), + campaigns_response(campaign_id=CAMPAIGN_ID, ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.campaigns_endpoint(AD_ACCOUNT_ID).build(), + campaigns_response(campaign_id=CAMPAIGN_ID, ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_campaigns_stats.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_campaigns_stats.py new file mode 100644 index 00000000000..d0bdefeea39 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_campaigns_stats.py @@ -0,0 +1,280 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, CAMPAIGN_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + campaigns_response, + create_multiple_records_response, + error_response, + oauth_response, + organizations_response, + stats_lifetime_response, + stats_timeseries_response, +) +from .utils import config, read_output + + +def _read( + config_builder: ConfigBuilder, + stream_name: str, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=stream_name, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +def _setup_parent_mocks(http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.campaigns_endpoint(AD_ACCOUNT_ID).build(), + campaigns_response(campaign_id=CAMPAIGN_ID, ad_account_id=AD_ACCOUNT_ID), + ) + + +def _setup_parent_mocks_multiple_campaigns(http_mocker: HttpMocker, campaign_ids: List[str]) -> None: + """Setup parent mocks with multiple campaigns for testing substreams.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.campaigns_endpoint(AD_ACCOUNT_ID).build(), + create_multiple_records_response("campaigns", campaign_ids), + ) + + +class TestCampaignsStatsHourly(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=CAMPAIGN_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_hourly") + + # Enhanced assertions + assert len(output.records) == 5 # 5 weekly time slices (Jan 1-31 with step: P1W) + record = output.records[0].record.data + assert record.get("id") == CAMPAIGN_ID, f"Expected id={CAMPAIGN_ID}, got {record.get('id')}" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + _setup_parent_mocks(http_mocker) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + [ + error_response(HTTPStatus.FORBIDDEN), + stats_timeseries_response(entity_id=CAMPAIGN_ID, granularity="HOUR"), + ], + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + + +class TestCampaignsStatsDaily(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=CAMPAIGN_ID, granularity="DAY"), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_daily") + + assert len(output.records) == 1 # Daily: step P1M = 1 monthly slice + record = output.records[0].record.data + assert record.get("id") == CAMPAIGN_ID, f"Expected id={CAMPAIGN_ID}, got {record.get('id')}" + + +class TestCampaignsStatsLifetime(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + stats_lifetime_response(entity_id=CAMPAIGN_ID), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_lifetime") + + assert len(output.records) == 1 # Lifetime: no step + record = output.records[0].record.data + assert record.get("id") == CAMPAIGN_ID, f"Expected id={CAMPAIGN_ID}, got {record.get('id')}" + + +class TestCampaignsStatsTransformations(TestCase): + @HttpMocker() + def test_transformations_add_fields(self, http_mocker: HttpMocker) -> None: + """Test that AddFields transformations are applied correctly. + + The manifest defines these transformations for campaigns_stats_hourly: + - AddFields: id (from stream_slice['id']) + - AddFields: type = CAMPAIGN + - AddFields: granularity = HOUR + - AddFields: spend (from record.get('stats', {}).get('spend')) + - RemoveFields: stats + """ + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=CAMPAIGN_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_hourly") + assert len(output.records) == 5 # 5 weekly time slices + + record = output.records[0].record.data + # Verify AddFields transformations + assert record.get("id") == CAMPAIGN_ID + assert record.get("type") == "CAMPAIGN" + assert record.get("granularity") == "HOUR" + # Verify spend field is extracted from stats + assert "spend" in record + # Verify RemoveFields transformation - stats should be removed + assert "stats" not in record + + +class TestCampaignsStatsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The campaigns_stats streams use SubstreamPartitionRouter with campaigns as parent. + This test verifies that stats are fetched for each parent campaign. + """ + campaign_1 = "campaign_001" + campaign_2 = "campaign_002" + + _setup_parent_mocks_multiple_campaigns(http_mocker, [campaign_1, campaign_2]) + + # Mock stats endpoint for each parent campaign + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(campaign_1).with_any_query_params().build(), + stats_timeseries_response(entity_id=campaign_1, granularity="HOUR"), + ) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(campaign_2).with_any_query_params().build(), + stats_timeseries_response(entity_id=campaign_2, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_hourly") + + # Verify records from both parent campaigns are returned + assert len(output.records) == 10 # 2 parents × 5 weekly time slices = 10 records + record_ids = [r.record.data.get("id") for r in output.records] + assert campaign_1 in record_ids + assert campaign_2 in record_ids + + +class TestCampaignsStatsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=CAMPAIGN_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_hourly", sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 5 # 5 weekly time slices + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state for stats streams.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state("campaigns_stats_hourly", {"start_time": previous_state_date}).build() + + _setup_parent_mocks(http_mocker) + http_mocker.get( + RequestBuilder.campaigns_stats_endpoint(CAMPAIGN_ID).with_any_query_params().build(), + stats_timeseries_response(entity_id=CAMPAIGN_ID, granularity="HOUR"), + ) + + output = _read(config_builder=config(), stream_name="campaigns_stats_hourly", sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 3 # 3 remaining weekly time slices after state date (Jan 15-31) + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("start_time") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("start_time") or new_state.get("state", {}).get("start_time") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'start_time' in state" + assert record_cursor_value is not None, "Expected 'start_time' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_creatives.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_creatives.py new file mode 100644 index 00000000000..60672137a3c --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_creatives.py @@ -0,0 +1,230 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + creatives_response, + error_response, + oauth_response, + organizations_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "creatives" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestCreatives(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.creatives_endpoint(AD_ACCOUNT_ID).build(), + creatives_response(creative_id="test_creative_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == "test_creative_123" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.creatives_endpoint(AD_ACCOUNT_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + creatives_response(creative_id="test_creative_123", ad_account_id=AD_ACCOUNT_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == "test_creative_123" + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestCreativesSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The creatives stream uses SubstreamPartitionRouter with adaccounts as parent. + This test verifies that creatives are fetched for each parent adaccount. + """ + adaccount_1 = "adaccount_001" + adaccount_2 = "adaccount_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple([adaccount_1, adaccount_2]), + ) + # Mock creatives endpoint for each parent adaccount + http_mocker.get( + RequestBuilder.creatives_endpoint(adaccount_1).build(), + creatives_response(creative_id="creative_from_adaccount_1", ad_account_id=adaccount_1), + ) + http_mocker.get( + RequestBuilder.creatives_endpoint(adaccount_2).build(), + creatives_response(creative_id="creative_from_adaccount_2", ad_account_id=adaccount_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent adaccounts are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "creative_from_adaccount_1" in record_ids + assert "creative_from_adaccount_2" in record_ids + + +class TestCreativesIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.creatives_endpoint(AD_ACCOUNT_ID).build(), + creatives_response(creative_id="test_creative_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.creatives_endpoint(AD_ACCOUNT_ID).build(), + creatives_response(creative_id="test_creative_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_media.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_media.py new file mode 100644 index 00000000000..16f0eb34107 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_media.py @@ -0,0 +1,230 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + error_response, + media_response, + oauth_response, + organizations_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "media" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestMedia(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.media_endpoint(AD_ACCOUNT_ID).build(), + media_response(media_id="test_media_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == "test_media_123" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.media_endpoint(AD_ACCOUNT_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + media_response(media_id="test_media_123", ad_account_id=AD_ACCOUNT_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == "test_media_123" + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestMediaSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The media stream uses SubstreamPartitionRouter with adaccounts as parent. + This test verifies that media are fetched for each parent adaccount. + """ + adaccount_1 = "adaccount_001" + adaccount_2 = "adaccount_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple([adaccount_1, adaccount_2]), + ) + # Mock media endpoint for each parent adaccount + http_mocker.get( + RequestBuilder.media_endpoint(adaccount_1).build(), + media_response(media_id="media_from_adaccount_1", ad_account_id=adaccount_1), + ) + http_mocker.get( + RequestBuilder.media_endpoint(adaccount_2).build(), + media_response(media_id="media_from_adaccount_2", ad_account_id=adaccount_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent adaccounts are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "media_from_adaccount_1" in record_ids + assert "media_from_adaccount_2" in record_ids + + +class TestMediaIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.media_endpoint(AD_ACCOUNT_ID).build(), + media_response(media_id="test_media_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.media_endpoint(AD_ACCOUNT_ID).build(), + media_response(media_id="test_media_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_organizations.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_organizations.py new file mode 100644 index 00000000000..30de3772550 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_organizations.py @@ -0,0 +1,198 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from datetime import datetime +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + create_empty_response, + error_response, + oauth_response, + organizations_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "organizations" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestOrganizations(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == ORGANIZATION_ID + + @HttpMocker() + def test_read_records_with_organization_ids(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint(ORGANIZATION_ID).build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config().with_organization_ids([ORGANIZATION_ID])) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == ORGANIZATION_ID + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + [ + error_response(HTTPStatus.FORBIDDEN), + organizations_response(organization_id=ORGANIZATION_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == ORGANIZATION_ID + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestOrganizationsEmptyResults(TestCase): + @HttpMocker() + def test_empty_results(self, http_mocker: HttpMocker) -> None: + """Test handling of 0-record responses from API (GAP 2).""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + create_empty_response("organizations"), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 0 + assert len(output.errors) == 0 + # Verify sync completed successfully + log_messages = [log.log.message for log in output.logs] + assert any("Finished syncing" in msg or "Read" in msg for msg in log_messages) + + +class TestOrganizationsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state. + + This test validates: + - Connector accepts state from previous sync + - State is passed to both get_source() and read() + - Records are returned + - State advances to latest record's cursor value + """ + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_segments.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_segments.py new file mode 100644 index 00000000000..d44d4b8d285 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/test_segments.py @@ -0,0 +1,230 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from typing import List, Optional +from unittest import TestCase + +from airbyte_cdk.models import AirbyteStateMessage, SyncMode +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput +from airbyte_cdk.test.mock_http import HttpMocker +from airbyte_cdk.test.state_builder import StateBuilder + +from .config import AD_ACCOUNT_ID, ORGANIZATION_ID, ConfigBuilder +from .request_builder import OAuthRequestBuilder, RequestBuilder +from .response_builder import ( + adaccounts_response, + adaccounts_response_multiple, + error_response, + oauth_response, + organizations_response, + segments_response, +) +from .utils import config, read_output + + +_STREAM_NAME = "segments" + + +def _read( + config_builder: ConfigBuilder, + sync_mode: SyncMode = SyncMode.full_refresh, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: bool = False, +) -> EntrypointOutput: + return read_output( + config_builder=config_builder, + stream_name=_STREAM_NAME, + sync_mode=sync_mode, + state=state, + expecting_exception=expecting_exception, + ) + + +class TestSegments(TestCase): + @HttpMocker() + def test_read_records(self, http_mocker: HttpMocker) -> None: + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.segments_endpoint(AD_ACCOUNT_ID).build(), + segments_response(segment_id="test_segment_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == "test_segment_123" + + @HttpMocker() + def test_read_records_with_error_403_retry(self, http_mocker: HttpMocker) -> None: + """Test that 403 errors trigger RETRY behavior with custom error message from manifest.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + # First request returns 403, then succeeds on retry + http_mocker.get( + RequestBuilder.segments_endpoint(AD_ACCOUNT_ID).build(), + [ + error_response(HTTPStatus.FORBIDDEN), + segments_response(segment_id="test_segment_123", ad_account_id=AD_ACCOUNT_ID), + ], + ) + + output = _read(config_builder=config()) + assert len(output.records) == 1 + assert output.records[0].record.data["id"] == "test_segment_123" + + # Verify custom error message from manifest is logged + log_messages = [log.log.message for log in output.logs] + expected_error_prefix = "Got permission error when accessing URL. Skipping" + assert any( + expected_error_prefix in msg for msg in log_messages + ), f"Expected custom 403 error message '{expected_error_prefix}' in logs" + assert any(_STREAM_NAME in msg for msg in log_messages), f"Expected stream name '{_STREAM_NAME}' in log messages" + + +class TestSegmentsSubstreamMultipleParents(TestCase): + @HttpMocker() + def test_substream_with_two_parent_records(self, http_mocker: HttpMocker) -> None: + """Test that substream correctly processes multiple parent records. + + The segments stream uses SubstreamPartitionRouter with adaccounts as parent. + This test verifies that segments are fetched for each parent adaccount. + """ + adaccount_1 = "adaccount_001" + adaccount_2 = "adaccount_002" + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response_multiple([adaccount_1, adaccount_2]), + ) + # Mock segments endpoint for each parent adaccount + http_mocker.get( + RequestBuilder.segments_endpoint(adaccount_1).build(), + segments_response(segment_id="segment_from_adaccount_1", ad_account_id=adaccount_1), + ) + http_mocker.get( + RequestBuilder.segments_endpoint(adaccount_2).build(), + segments_response(segment_id="segment_from_adaccount_2", ad_account_id=adaccount_2), + ) + + output = _read(config_builder=config()) + + # Verify records from both parent adaccounts are returned + assert len(output.records) == 2 + record_ids = [r.record.data.get("id") for r in output.records] + assert "segment_from_adaccount_1" in record_ids + assert "segment_from_adaccount_2" in record_ids + + +class TestSegmentsIncremental(TestCase): + @HttpMocker() + def test_incremental_first_sync_emits_state(self, http_mocker: HttpMocker) -> None: + """Test that first sync (no state) emits state message with cursor value.""" + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.segments_endpoint(AD_ACCOUNT_ID).build(), + segments_response(segment_id="test_segment_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" + + @HttpMocker() + def test_incremental_sync_with_state(self, http_mocker: HttpMocker) -> None: + """Test incremental sync with previous state.""" + previous_state_date = "2024-01-15T00:00:00.000000Z" + state = StateBuilder().with_stream_state(_STREAM_NAME, {"updated_at": previous_state_date}).build() + + http_mocker.post( + OAuthRequestBuilder.oauth_endpoint().build(), + oauth_response(), + ) + http_mocker.get( + RequestBuilder.organizations_endpoint("me").build(), + organizations_response(organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.adaccounts_endpoint(ORGANIZATION_ID).build(), + adaccounts_response(ad_account_id=AD_ACCOUNT_ID, organization_id=ORGANIZATION_ID), + ) + http_mocker.get( + RequestBuilder.segments_endpoint(AD_ACCOUNT_ID).build(), + segments_response(segment_id="test_segment_123", ad_account_id=AD_ACCOUNT_ID), + ) + + output = _read(config_builder=config(), sync_mode=SyncMode.incremental, state=state) + + assert len(output.state_messages) > 0, "Expected state messages to be emitted" + assert len(output.records) == 1 + + # Get latest record's cursor + latest_record = output.records[-1].record.data + record_cursor_value = latest_record.get("updated_at") + + # Get state cursor + new_state = output.most_recent_state.stream_state.__dict__ + state_cursor_value = new_state.get("updated_at") or new_state.get("state", {}).get("updated_at") + + # Validate state matches record + assert state_cursor_value is not None, "Expected 'updated_at' in state" + assert record_cursor_value is not None, "Expected 'updated_at' in record" + assert state_cursor_value == record_cursor_value or state_cursor_value.startswith( + record_cursor_value[:10] + ), f"Expected state to match latest record. State: {state_cursor_value}, Record: {record_cursor_value}" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/utils.py b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/utils.py new file mode 100644 index 00000000000..5118313edc6 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/integration/utils.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +# + +from typing import List, Optional + +from airbyte_cdk.models import AirbyteStateMessage, ConfiguredAirbyteCatalog, SyncMode +from airbyte_cdk.test.catalog_builder import CatalogBuilder +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput, read + +from ..conftest import get_source +from .config import ConfigBuilder + + +def catalog(stream_name: str, sync_mode: SyncMode) -> ConfiguredAirbyteCatalog: + return CatalogBuilder().with_stream(stream_name, sync_mode).build() + + +def config() -> ConfigBuilder: + return ConfigBuilder() + + +def read_output( + config_builder: ConfigBuilder, + stream_name: str, + sync_mode: SyncMode, + state: Optional[List[AirbyteStateMessage]] = None, + expecting_exception: Optional[bool] = False, +) -> EntrypointOutput: + _catalog = catalog(stream_name, sync_mode) + _config = config_builder.build() + # Pass state to BOTH get_source() and read() for proper incremental sync behavior + source = get_source(config=_config, state=state) + return read(source, _config, _catalog, state, expecting_exception) diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/poetry.lock b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/poetry.lock new file mode 100644 index 00000000000..a14f5167e82 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/poetry.lock @@ -0,0 +1,3037 @@ +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "airbyte-cdk" +version = "6.61.6" +description = "A framework for writing Airbyte Connectors." +optional = false +python-versions = "<3.14,>=3.10" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "airbyte_cdk-6.61.6-py3-none-any.whl", hash = "sha256:8890a4428d3501409f7a0e85f8734997367ea5d229f2c7a55873ef6cf334fec3"}, + {file = "airbyte_cdk-6.61.6.tar.gz", hash = "sha256:f81809ecedf6108886a34d84544496037861780b3bded064899262d4b9349a5e"}, +] + +[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 = ">=0.2,<0.3" +jsonschema = ">=4.17.3,<5.0" +langchain_core = "0.1.42" +nltk = "3.9.1" +numpy = "<2" +orjson = ">=3.10.7,<4.0.0" +packaging = "*" +pandas = "2.2.3" +psutil = "6.1.0" +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.6.16,<0.7.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.12.3,<4.0.0)", "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 (==0.1.16)", "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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "anyio" +version = "4.12.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb"}, + {file = "anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +trio = ["trio (>=0.31.0)", "trio (>=0.32.0)"] + +[[package]] +name = "attributes-doc" +version = "0.4.0" +description = "PEP 224 implementation" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "25.4.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, +] + +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.6" +description = "Bash style brace expander." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952"}, + {file = "bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7"}, +] + +[[package]] +name = "cachetools" +version = "6.2.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace"}, + {file = "cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6"}, +] + +[[package]] +name = "cattrs" +version = "25.3.0" +description = "Composable complex class support for attrs and dataclasses." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff"}, + {file = "cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a"}, +] + +[package.dependencies] +attrs = ">=25.4.0" +exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.14.0" + +[package.extras] +bson = ["pymongo (>=4.4.0)"] +cbor2 = ["cbor2 (>=5.4.6)"] +msgpack = ["msgpack (>=1.0.5)"] +msgspec = ["msgspec (>=0.19.0)"] +orjson = ["orjson (>=3.11.3)"] +pyyaml = ["pyyaml (>=6.0)"] +tomlkit = ["tomlkit (>=0.11.8)"] +ujson = ["ujson (>=5.10.0)"] + +[[package]] +name = "certifi" +version = "2025.11.12" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, +] + +[[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 = "(python_version <= \"3.11\" or python_version >= \"3.12\") and 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.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + +[[package]] +name = "click" +version = "8.3.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "(platform_system == \"Windows\" or sys_platform == \"win32\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "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 = "dateparser" +version = "1.2.2" +description = "Date parsing library designed to parse dates from HTML pages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "freezegun" +version = "1.5.5" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2"}, + {file = "freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "genson" +version = "1.3.0" +description = "GenSON is a powerful, user-friendly JSON Schema generator." +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "python_version < \"3.11\" and 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 = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, +] +proto-plus = ">=1.22.3,<2.0.0" +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)", "grpcio (>=1.75.1,<2.0.0)", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0)", "grpcio-status (>=1.75.1,<2.0.0)"] +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0)", "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)", "cryptography (<39.0.0)", "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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = ">=1.22.3,<2.0.0" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "jsonref" +version = "0.2" +description = "An implementation of JSON Reference for Python" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "jsonref-0.2-py3-none-any.whl", hash = "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f"}, + {file = "jsonref-0.2.tar.gz", hash = "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697"}, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "langchain-core" +version = "0.1.42" +description = "Building applications with LLMs through composability" +optional = false +python-versions = "<4.0,>=3.8.1" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "langchain_core-0.1.42-py3-none-any.whl", hash = "sha256:c5653ffa08a44f740295c157a24c0def4a753333f6a2c41f76bf431cd00be8b5"}, + {file = "langchain_core-0.1.42.tar.gz", hash = "sha256:40751bf60ea5d8e2b2efe65290db434717ee3834870c002e40e2811f09d814e6"}, +] + +[package.dependencies] +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.1.0,<0.2.0" +packaging = ">=23.2,<24.0" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +extended-testing = ["jinja2 (>=3,<4)"] + +[[package]] +name = "langsmith" +version = "0.1.147" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = "<4.0,>=3.8.1" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"}, + {file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<1" +orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} +pydantic = [ + {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +requests = ">=2,<3" +requests-toolbelt = ">=1.0.0,<2.0.0" + +[package.extras] +langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] + +[[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "mock" +version = "5.2.0" +description = "Rolling backport of unittest.mock for all Pythons" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, + {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, +] + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "nltk" +version = "3.9.1" +description = "Natural Language Toolkit" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "orjson" +version = "3.11.4" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "orjson-3.11.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e3aa2118a3ece0d25489cbe48498de8a5d580e42e8d9979f65bf47900a15aba1"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a69ab657a4e6733133a3dca82768f2f8b884043714e8d2b9ba9f52b6efef5c44"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3740bffd9816fc0326ddc406098a3a8f387e42223f5f455f2a02a9f834ead80c"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65fd2f5730b1bf7f350c6dc896173d3460d235c4be007af73986d7cd9a2acd23"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fdc3ae730541086158d549c97852e2eea6820665d4faf0f41bf99df41bc11ea"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e10b4d65901da88845516ce9f7f9736f9638d19a1d483b3883dc0182e6e5edba"}, + {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6a03a678085f64b97f9d4a9ae69376ce91a3a9e9b56a82b1580d8e1d501aff"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c82e4f0b1c712477317434761fbc28b044c838b6b1240d895607441412371ac"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d58c166a18f44cc9e2bad03a327dc2d1a3d2e85b847133cfbafd6bfc6719bd79"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94f206766bf1ea30e1382e4890f763bd1eefddc580e08fec1ccdc20ddd95c827"}, + {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:41bf25fb39a34cf8edb4398818523277ee7096689db352036a9e8437f2f3ee6b"}, + {file = "orjson-3.11.4-cp310-cp310-win32.whl", hash = "sha256:fa9627eba4e82f99ca6d29bc967f09aba446ee2b5a1ea728949ede73d313f5d3"}, + {file = "orjson-3.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:23ef7abc7fca96632d8174ac115e668c1e931b8fe4dde586e92a500bf1914dcc"}, + {file = "orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39"}, + {file = "orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a"}, + {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905"}, + {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907"}, + {file = "orjson-3.11.4-cp311-cp311-win32.whl", hash = "sha256:87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c"}, + {file = "orjson-3.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a"}, + {file = "orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045"}, + {file = "orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50"}, + {file = "orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708"}, + {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c"}, + {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9"}, + {file = "orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa"}, + {file = "orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140"}, + {file = "orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e"}, + {file = "orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534"}, + {file = "orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9"}, + {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a"}, + {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6"}, + {file = "orjson-3.11.4-cp313-cp313-win32.whl", hash = "sha256:6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839"}, + {file = "orjson-3.11.4-cp313-cp313-win_amd64.whl", hash = "sha256:09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a"}, + {file = "orjson-3.11.4-cp313-cp313-win_arm64.whl", hash = "sha256:a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de"}, + {file = "orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:42d43a1f552be1a112af0b21c10a5f553983c2a0938d2bbb8ecd8bc9fb572803"}, + {file = "orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:26a20f3fbc6c7ff2cb8e89c4c5897762c9d88cf37330c6a117312365d6781d54"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3f20be9048941c7ffa8fc523ccbd17f82e24df1549d1d1fe9317712d19938e"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aac364c758dc87a52e68e349924d7e4ded348dedff553889e4d9f22f74785316"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5c54a6d76e3d741dcc3f2707f8eeb9ba2a791d3adbf18f900219b62942803b1"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28485bdca8617b79d44627f5fb04336897041dfd9fa66d383a49d09d86798bc"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfc2a484cad3585e4ba61985a6062a4c2ed5c7925db6d39f1fa267c9d166487f"}, + {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e34dbd508cb91c54f9c9788923daca129fe5b55c5b4eebe713bf5ed3791280cf"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b13c478fa413d4b4ee606ec8e11c3b2e52683a640b006bb586b3041c2ca5f606"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:724ca721ecc8a831b319dcd72cfa370cc380db0bf94537f08f7edd0a7d4e1780"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:977c393f2e44845ce1b540e19a786e9643221b3323dae190668a98672d43fb23"}, + {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e539e382cf46edec157ad66b0b0872a90d829a6b71f17cb633d6c160a223155"}, + {file = "orjson-3.11.4-cp314-cp314-win32.whl", hash = "sha256:d63076d625babab9db5e7836118bdfa086e60f37d8a174194ae720161eb12394"}, + {file = "orjson-3.11.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a54d6635fa3aaa438ae32e8570b9f0de36f3f6562c308d2a2a452e8b0592db1"}, + {file = "orjson-3.11.4-cp314-cp314-win_arm64.whl", hash = "sha256:78b999999039db3cf58f6d230f524f04f75f129ba3d1ca2ed121f8657e575d3d"}, + {file = "orjson-3.11.4-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:405261b0a8c62bcbd8e2931c26fdc08714faf7025f45531541e2b29e544b545b"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af02ff34059ee9199a3546f123a6ab4c86caf1708c79042caf0820dc290a6d4f"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b2eba969ea4203c177c7b38b36c69519e6067ee68c34dc37081fac74c796e10"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0baa0ea43cfa5b008a28d3c07705cf3ada40e5d347f0f44994a64b1b7b4b5350"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80fd082f5dcc0e94657c144f1b2a3a6479c44ad50be216cf0c244e567f5eae19"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3704d35e47d5bee811fb1cbd8599f0b4009b14d451c4c57be5a7e25eb89a13"}, + {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa447f2b5356779d914658519c874cf3b7629e99e63391ed519c28c8aea4919"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bba5118143373a86f91dadb8df41d9457498226698ebdf8e11cbb54d5b0e802d"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:622463ab81d19ef3e06868b576551587de8e4d518892d1afab71e0fbc1f9cffc"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3e0a700c4b82144b72946b6629968df9762552ee1344bfdb767fecdd634fbd5a"}, + {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6e18a5c15e764e5f3fc569b47872450b4bcea24f2a6354c0a0e95ad21045d5a9"}, + {file = "orjson-3.11.4-cp39-cp39-win32.whl", hash = "sha256:fb1c37c71cad991ef4d89c7a634b5ffb4447dbd7ae3ae13e8f5ee7f1775e7ab1"}, + {file = "orjson-3.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:e2985ce8b8c42d00492d0ed79f2bd2b6460d00f2fa671dfde4bf2e02f49bf5c6"}, + {file = "orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.5.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, + {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, +] + +[package.extras] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "proto-plus" +version = "1.26.1" +description = "Beautiful, Pythonic protocol buffers" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.1" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b"}, + {file = "protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed"}, + {file = "protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1"}, + {file = "protobuf-6.33.1-cp39-cp39-win32.whl", hash = "sha256:023af8449482fa884d88b4563d85e83accab54138ae098924a985bcbb734a213"}, + {file = "protobuf-6.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:df051de4fd7e5e4371334e234c62ba43763f15ab605579e04c7008c05735cd82"}, + {file = "protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa"}, + {file = "protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b"}, +] + +[[package]] +name = "psutil" +version = "6.1.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, +] + +[package.extras] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "pycparser" +version = "2.23" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\" and (python_version <= \"3.11\" or python_version >= \"3.12\")" +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.12.5" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.3" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.3" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "requests-mock" +version = "1.12.1" +description = "Mock out responses from the requests package" +optional = false +python-versions = ">=3.5" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, + {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, +] + +[package.dependencies] +requests = ">=2.22,<3" + +[package.extras] +fixture = ["fixtures"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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)", "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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "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)", "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", "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)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, + {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "tomli" +version = "2.3.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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 = "2.2.1" +description = "URL normalization for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "url_normalize-2.2.1-py3-none-any.whl", hash = "sha256:3deb687587dc91f7b25c9ae5162ffc0f057ae85d22b1e15cf5698311247f567b"}, + {file = "url_normalize-2.2.1.tar.gz", hash = "sha256:74a540a3b6eba1d95bdc610c24f2c0141639f3ba903501e61a52a8730247ff37"}, +] + +[package.dependencies] +idna = ">=3.3" + +[package.extras] +dev = ["mypy", "pre-commit", "pytest", "pytest-cov", "pytest-socket", "ruff"] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.6.17" +description = "Modern datetime library for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "whenever-0.6.17-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8e9e905fd19b0679e5ab1a0d0110a1974b89bf4cbd1ff22c9e352db381e4ae4f"}, + {file = "whenever-0.6.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cd615e60f992fb9ae9d73fc3581ac63de981e51013b0fffbf8e2bd748c71e3df"}, + {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd717faa660771bf6f2fda4f75f2693cd79f2a7e975029123284ea3859fb329c"}, + {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2ea744d9666be8880062da0d6dee690e8f70a2bc2a42b96ee17e10e36b0b5266"}, + {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6b32593b44332660402c7e4c681cce6d7859b15a609d66ac3a28a6ad6357c2f"}, + {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a01e4daaac24e0be48a6cb0bb03fa000a40126b1e9cb8d721ee116b2f44c1bb1"}, + {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e88fe9fccb868ee88bb2ee8bfcbc55937d0b40747069f595f10b4832ff1545"}, + {file = "whenever-0.6.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2dce7b9faf23325b38ca713b2c7a150a8befc832995213a8ec46fe15af6a03e7"}, + {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0925f7bf3448ef4f8c9b93de2d1270b82450a81b5d025a89f486ea61aa94319"}, + {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:82203a572049070d685499dd695ff1914fee62f32aefa9e9952a60762217aa9e"}, + {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c30e5b5b82783bc85169c8208ab3acf58648092515017b2a185a598160503dbb"}, + {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:763e59062adc9adfbde45c3ad8b5f472b337cc5cebc70760627d004a4c286d33"}, + {file = "whenever-0.6.17-cp310-cp310-win32.whl", hash = "sha256:f71387bbe95cd98fc78653b942c6e02ff4245b6add012b3f11796220272984ce"}, + {file = "whenever-0.6.17-cp310-cp310-win_amd64.whl", hash = "sha256:996ab1f6f09bc9e0c699fa58937b5adc25e39e979ebbebfd77bae09221350f3d"}, + {file = "whenever-0.6.17-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:87e28378945182e822e211fcea9e89c7428749fd440b616d6d81365202cbed09"}, + {file = "whenever-0.6.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf4ee3e8d5a55d788e8a79aeff29482dd4facc38241901f18087c3e662d16ba"}, + {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97ffc43cd278f6f58732cd9d83c822faff3b1987c3b7b448b59b208cf6b6293"}, + {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ce99533865fd63029fa64aef1cfbd42be1d2ced33da38c82f8c763986583982"}, + {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68b88e023d64e8ccfabe04028738d8041eccd5a078843cd9b506e51df3375e84"}, + {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9159bae31f2edaf5e70e4437d871e52f51e7e90f1b9faaac19a8c2bccba5170a"}, + {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f9c4ee1f1e85f857507d146d56973db28d148f50883babf1da3d24a40bbcf60"}, + {file = "whenever-0.6.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0acd8b3238aa28a20d1f93c74fd84c9b59e2662e553a55650a0e663a81d2908d"}, + {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ae238cd46567b5741806517d307a81cca45fd49902312a9bdde27db5226e8825"}, + {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:99f72853e8292284c2a89a06ab826892216c04540a0ca84b3d3eaa9317dbe026"}, + {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ccb6c77b497d651a283ef0f40ada326602b313ee71d22015f53d5496124dfc10"}, + {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a1918c9836dc331cd9a39175806668b57b93d538d288469ad8bedb144ec11b"}, + {file = "whenever-0.6.17-cp311-cp311-win32.whl", hash = "sha256:72492f130a8c5b8abb2d7b16cec33b6d6ed9e294bb63c56ab1030623de4ae343"}, + {file = "whenever-0.6.17-cp311-cp311-win_amd64.whl", hash = "sha256:88dc4961f8f6cd16d9b70db022fd6c86193fad429f98daeb82c8e9ba0ca27e5c"}, + {file = "whenever-0.6.17-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d72c2413e32e3f382f6def337961ea7f20e66d0452ebc02e2fa215e1c45df73e"}, + {file = "whenever-0.6.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d12b891d780d9c98585b507e9f85097085337552b75f160ce6930af96509faa1"}, + {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:503aaf2acfd5a7926ca5c6dc6ec09fc6c2891f536ab9cbd26a072c94bda3927f"}, + {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6de09bcddfeb61c822019e88d8abed9ccc1d4f9d1a3a5d62d28d94d2fb6daff5"}, + {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfe430df7f336d8793b6b844f0d2552e1589e39e72b7414ba67139b9b402bed"}, + {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99776635ac174a3df4a372bfae7420b3de965044d69f2bee08a7486cabba0aaa"}, + {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbb6d8dae94b492370949c8d8bf818f9ee0b4a08f304dadf9d6d892b7513676"}, + {file = "whenever-0.6.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:45d66e68cdca52ca3e6e4990515d32f6bc4eb6a24ff8cbcbe4df16401dd2d3c7"}, + {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73947bd633bc658f8a8e2ff2bff34ee7caabd6edd9951bb2d778e6071c772df4"}, + {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9f9d5b108f9abf39471e3d5ef22ff2fed09cc51a0cfa63c833c393b21b8bdb81"}, + {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a42231e7623b50a60747a752a97499f6ad03e03ce128bf97ded84e12b0f4a77e"}, + {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a6d9458d544006131e1210343bf660019abfa11d46f5be8ad2d7616dc82340f4"}, + {file = "whenever-0.6.17-cp312-cp312-win32.whl", hash = "sha256:ca1eda94ca2ef7ad1a1249ea80949be252e78a0f10463e12c81ad126ec6b99e5"}, + {file = "whenever-0.6.17-cp312-cp312-win_amd64.whl", hash = "sha256:fd7de20d6bbb74c6bad528c0346ef679957db21ce8a53f118e53b5f60f76495b"}, + {file = "whenever-0.6.17-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca9ee5b2b04c5a65112f55ff4a4efcba185f45b95766b669723e8b9a28bdb50b"}, + {file = "whenever-0.6.17-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bef0cf1cd4282044d98e4af9969239dc139e5b192896d4110d0d3f4139bdb30"}, + {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04ac4e1fc1bc0bfb35f2c6a05d52de9fec297ea84ee60c655dec258cca1e6eb7"}, + {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c792f96d021ba2883e6f4b70cc58b5d970f026eb156ff93866686e27a7cce93"}, + {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a7f938b5533e751702de95a615b7903457a7618b94aef72c062fa871ad691b"}, + {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47d2dbb85c512e28c14eede36a148afbb90baa340e113b39b2b9f0e9a3b192dd"}, + {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2b49a91853c133e8954dffbf180adca539b3719fd269565bf085ba97b47f5f"}, + {file = "whenever-0.6.17-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:91fcb2f42381a8ad763fc7ee2259375b1ace1306a02266c195af27bd3696e0da"}, + {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e4d5e3429015a5082cd171ceea633c6ea565d90491005cdcef49a7d6a17c99"}, + {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f05731f530e4af29582a70cf02f8441027a4534e67b7c484efdf210fc09d0421"}, + {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0d417b7de29aea2cfa7ea47f344848491d44291f28c038df869017ae66a50b48"}, + {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8208333ece7f2e0c232feeecbd21bde3888c6782d3b08372ae8b5269938645b3"}, + {file = "whenever-0.6.17-cp313-cp313-win32.whl", hash = "sha256:c4912104731fd2be89cd031d8d34227225f1fae5181f931b91f217e69ded48ff"}, + {file = "whenever-0.6.17-cp313-cp313-win_amd64.whl", hash = "sha256:4f46ad87fab336d7643e0c2248dcd27a0f4ae42ac2c5e864a9d06a8f5538efd0"}, + {file = "whenever-0.6.17-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:53f03ae8c54aa60f5f22c790eb63ad644e97f8fba4b22337572a4e16bc4abb73"}, + {file = "whenever-0.6.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42fce832892578455d46870dc074521e627ba9272b839a8297784059170030f5"}, + {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ac0786d6cb479275ea627d84536f38b6a408348961856e2e807d82d4dc768ed"}, + {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e2f490b5e90b314cf7615435e24effe2356b57fa907fedb98fe58d49c6109c5"}, + {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c1f25ab893cfa724b319a838ef60b918bd35be8f3f6ded73e6fd6e508b5237e"}, + {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac5f644d0d3228e806b5129cebfb824a5e26553a0d47d89fc9e962cffa1b99ed"}, + {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185309314b1abcc14c18597dd0dfe7fd8b39670f63a7d9357544994cba0e251"}, + {file = "whenever-0.6.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cc78b8a73a71241bf356743dd76133ccf796616823d8bbe170701a51d10b9fd3"}, + {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0ea05123a0b3673c7cf3ea1fe3d8aa9362571db59f8ea15d7a8fb05d885fd756"}, + {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9f0c874dbb49c3a733ce4dde86ffa243f166b9d1db4195e05127ec352b49d617"}, + {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:86cfbd724b11e8a419056211381bde4c1d35ead4bea8d498c85bee3812cf4e7c"}, + {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e1514f4a3094f11e1ad63b9defadf375d953709c7806cc1d2396634a7b00a009"}, + {file = "whenever-0.6.17-cp39-cp39-win32.whl", hash = "sha256:715ed172e929327c1b68e107f0dc9520237d92e11c26db95fd05869724f3e9d9"}, + {file = "whenever-0.6.17-cp39-cp39-win_amd64.whl", hash = "sha256:5fed15042b2b0ea44cafb8b7426e99170d3f4cd64dbeb966c77f14985e724d82"}, + {file = "whenever-0.6.17.tar.gz", hash = "sha256:9c4bfe755c8f06726c4031dbbecd0a7710e2058bc2f3b4e4e331755af015f55f"}, +] + +[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"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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.13" +content-hash = "6c643c93c4f1a38e8f188bf170f7902ac49a4099169b2623e5d16f4487fb5945" diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/pyproject.toml b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/pyproject.toml new file mode 100644 index 00000000000..f7851a75e39 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "source-snapchat-marketing" +version = "0.0.0" +description = "Unit tests for source-snapchat-marketing" +authors = ["Airbyte "] + +[tool.poetry.dependencies] +python = "^3.10,<3.13" +airbyte-cdk = "^6" +pytest = "^8" +freezegun = "^1.4.0" +pytest-mock = "^3.6.1" +requests-mock = "^1.12.1" +mock = "^5.1.0" + +[tool.pytest.ini_options] +filterwarnings = [ + "ignore:This class is experimental*" +] diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/adaccounts.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/adaccounts.json new file mode 100644 index 00000000000..23897d6b972 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/adaccounts.json @@ -0,0 +1,27 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "adaccounts": [ + { + "sub_request_status": "SUCCESS", + "adaccount": { + "id": "test_adaccount_456", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Ad Account", + "type": "PARTNER", + "status": "ACTIVE", + "organization_id": "test_org_123", + "currency": "USD", + "timezone": "America/Los_Angeles", + "advertiser_organization_id": "test_org_123", + "advertiser": "Test Advertiser", + "billing_type": "IO", + "billing_center_id": "test_billing_center", + "lifetime_spend_cap_micro": 0, + "agency_representing_client": false, + "client_paying_invoices": false + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/ads.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/ads.json new file mode 100644 index 00000000000..5f491e1b3f5 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/ads.json @@ -0,0 +1,22 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "ads": [ + { + "sub_request_status": "SUCCESS", + "ad": { + "id": "test_ad_345", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Ad", + "ad_squad_id": "test_adsquad_012", + "creative_id": "test_creative_123", + "status": "ACTIVE", + "type": "SNAP_AD", + "render_type": "STATIC", + "review_status": "APPROVED", + "review_status_reasons": [] + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/adsquads.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/adsquads.json new file mode 100644 index 00000000000..d522dcd4522 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/adsquads.json @@ -0,0 +1,27 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "adsquads": [ + { + "sub_request_status": "SUCCESS", + "adsquad": { + "id": "test_adsquad_012", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Ad Squad", + "status": "ACTIVE", + "campaign_id": "test_campaign_789", + "type": "SNAP_ADS", + "targeting": {}, + "targeting_reach_status": "VALID", + "placement": "SNAP_ADS", + "billing_event": "IMPRESSION", + "auto_bid": true, + "bid_strategy": "AUTO_BID", + "daily_budget_micro": 50000000, + "start_time": "2024-01-01T00:00:00.000Z", + "optimization_goal": "IMPRESSIONS" + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/campaigns.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/campaigns.json new file mode 100644 index 00000000000..6427ff0e9b9 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/campaigns.json @@ -0,0 +1,24 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "campaigns": [ + { + "sub_request_status": "SUCCESS", + "campaign": { + "id": "test_campaign_789", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Campaign", + "ad_account_id": "test_adaccount_456", + "status": "ACTIVE", + "objective": "AWARENESS", + "start_time": "2024-01-01T00:00:00.000Z", + "end_time": "2024-12-31T23:59:59.000Z", + "daily_budget_micro": 100000000, + "lifetime_spend_cap_micro": 0, + "buy_model": "AUCTION", + "regulations": {} + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/creatives.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/creatives.json new file mode 100644 index 00000000000..7e93e05d2e6 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/creatives.json @@ -0,0 +1,25 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "creatives": [ + { + "sub_request_status": "SUCCESS", + "creative": { + "id": "test_creative_123", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Creative", + "ad_account_id": "test_adaccount_456", + "type": "SNAP_AD", + "packaging_status": "SUCCESS", + "review_status": "APPROVED", + "shareable": true, + "headline": "Test Headline", + "brand_name": "Test Brand", + "call_to_action": "INSTALL_NOW", + "top_snap_media_id": "test_media_id", + "top_snap_crop_position": "MIDDLE" + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/error_401.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/error_401.json new file mode 100644 index 00000000000..98214ce5ab7 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/error_401.json @@ -0,0 +1,5 @@ +{ + "request_status": "ERROR", + "request_id": "test_request_id", + "msg": "Unauthorized" +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/error_429.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/error_429.json new file mode 100644 index 00000000000..451031b0d6d --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/error_429.json @@ -0,0 +1,5 @@ +{ + "request_status": "ERROR", + "request_id": "test_request_id", + "msg": "Rate limit exceeded" +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/media.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/media.json new file mode 100644 index 00000000000..7b6d462aaf2 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/media.json @@ -0,0 +1,21 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "media": [ + { + "sub_request_status": "SUCCESS", + "media": { + "id": "test_media_123", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Media", + "ad_account_id": "test_adaccount_456", + "type": "VIDEO", + "media_status": "READY", + "file_name": "test_video.mp4", + "download_link": "https://example.com/test_video.mp4", + "duration_secs": 10.5 + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/oauth_token.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/oauth_token.json new file mode 100644 index 00000000000..9b171157c8d --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/oauth_token.json @@ -0,0 +1,7 @@ +{ + "access_token": "test_access_token", + "token_type": "Bearer", + "expires_in": 1800, + "refresh_token": "test_refresh_token", + "scope": "snapchat-marketing-api" +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/organizations.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/organizations.json new file mode 100644 index 00000000000..9070e3c611b --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/organizations.json @@ -0,0 +1,28 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "organizations": [ + { + "sub_request_status": "SUCCESS", + "organization": { + "id": "test_org_123", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Organization", + "address_line_1": "123 Test St", + "locality": "Test City", + "administrative_district_level_1": "CA", + "country": "US", + "postal_code": "12345", + "type": "ENTERPRISE", + "state": "ACTIVE", + "configuration_settings": {}, + "accepted_term_version": "1", + "contact_name": "Test Contact", + "contact_email": "test@example.com", + "contact_phone": "+1234567890", + "roles": ["ADMIN"] + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/segments.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/segments.json new file mode 100644 index 00000000000..1429647c52e --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/segments.json @@ -0,0 +1,25 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "segments": [ + { + "sub_request_status": "SUCCESS", + "segment": { + "id": "test_segment_123", + "updated_at": "2024-01-15T10:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "name": "Test Segment", + "ad_account_id": "test_adaccount_456", + "description": "Test segment description", + "status": "ACTIVE", + "source_type": "FIRST_PARTY", + "retention_in_days": 180, + "approximate_number_users": 1000, + "upload_status": "COMPLETE", + "targetable_status": "READY", + "organization_id": "test_org_123", + "visible_to": ["ALL_ACCOUNTS"] + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/stats_lifetime.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/stats_lifetime.json new file mode 100644 index 00000000000..5b45e6df053 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/stats_lifetime.json @@ -0,0 +1,23 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "lifetime_stats": [ + { + "sub_request_status": "SUCCESS", + "lifetime_stat": { + "id": "test_entity_id", + "type": "AD_ACCOUNT", + "granularity": "LIFETIME", + "stats": { + "impressions": 100000, + "swipes": 5000, + "spend": 500000000, + "video_views": 80000, + "android_installs": 1000, + "ios_installs": 1500, + "total_installs": 2500 + } + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/stats_timeseries.json b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/stats_timeseries.json new file mode 100644 index 00000000000..5abc9284096 --- /dev/null +++ b/airbyte-integrations/connectors/source-snapchat-marketing/unit_tests/resource/http/response/stats_timeseries.json @@ -0,0 +1,31 @@ +{ + "request_status": "SUCCESS", + "request_id": "test_request_id", + "timeseries_stats": [ + { + "sub_request_status": "SUCCESS", + "timeseries_stat": { + "id": "test_entity_id", + "type": "AD_ACCOUNT", + "granularity": "HOUR", + "start_time": "2024-01-15T00:00:00.000-0800", + "end_time": "2024-01-15T01:00:00.000-0800", + "timeseries": [ + { + "start_time": "2024-01-15T00:00:00.000-0800", + "end_time": "2024-01-15T01:00:00.000-0800", + "stats": { + "impressions": 1000, + "swipes": 50, + "spend": 5000000, + "video_views": 800, + "android_installs": 10, + "ios_installs": 15, + "total_installs": 25 + } + } + ] + } + } + ] +}